Compare commits

..

1 Commits

Author SHA1 Message Date
Daniel O'Connor
219867099a Attempt to restyle 2025-08-29 12:26:26 +00:00
151 changed files with 2989 additions and 5268 deletions

View File

@@ -27,7 +27,7 @@ services:
command: sleep infinity
db:
image: postgres:17
image: postgres:latest
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (admin/)
run: bundle exec rspec spec/features/admin/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (comments/)
run: bundle exec rspec spec/features/comments/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -101,9 +101,3 @@ jobs:
- name: Run rspec (conversations/)
run: bundle exec rspec spec/features/conversations/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (crops/)
run: bundle exec rspec spec/features/crops/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (gardens/)
run: bundle exec rspec spec/features/gardens/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -99,11 +99,4 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (harvests/)
run: bundle exec rspec spec/features/harvests/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots
run: bundle exec rspec spec/features/harvests/ -fd -t ~@flaky

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -99,11 +99,4 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (home/)
run: bundle exec rspec spec/features/home/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots
run: bundle exec rspec spec/features/home/ -fd -t ~@flaky

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -99,11 +99,4 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (members/)
run: bundle exec rspec spec/features/members/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots
run: bundle exec rspec spec/features/members/ -fd -t ~@flaky

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (places/)
run: bundle exec rspec spec/features/places/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (plantings/)
run: bundle exec rspec spec/features/plantings/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -1,4 +1,4 @@
name: CI Features - Posts
name: CI Features - Admin
on: [pull_request]
@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (posts/)
run: bundle exec rspec spec/features/posts/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -100,10 +100,3 @@ jobs:
- name: Run rspec (seeds/)
run: bundle exec rspec spec/features/seeds/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -99,11 +99,4 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (timeline/)
run: bundle exec rspec spec/features/timeline/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots
run: bundle exec rspec spec/features/timeline/ -fd -t ~@flaky

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -74,7 +74,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'
@@ -108,11 +108,4 @@ jobs:
run: bundle exec rspec spec/features/photos/ -fd
- name: Run rspec (rss/)
run: bundle exec rspec spec/features/rss/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: screenshots
path: tmp/screenshots
run: bundle exec rspec spec/features/rss/ -fd

View File

@@ -6,7 +6,7 @@ jobs:
contributors:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Install ruby version specified in .ruby-version
uses: ruby/setup-ruby@v1
with:
@@ -53,7 +53,7 @@ jobs:
steps:
- name: Checkout this repo
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Configure sysctl limits
run: |
@@ -89,7 +89,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '12'

View File

@@ -1,5 +1,5 @@
inherit_from: .rubocop_todo.yml
plugins:
require:
- rubocop-factory_bot
- rubocop-capybara
- rubocop-rails

View File

@@ -314,7 +314,7 @@ RSpec/MultipleExpectations:
# Offense count: 138
# Configuration parameters: AllowSubject.
RSpec/MultipleMemoizedHelpers:
Max: 20
Max: 14
# Offense count: 133
# Configuration parameters: EnforcedStyle, IgnoreSharedExamples.

View File

@@ -178,6 +178,7 @@ group :development, :test do
gem 'dotenv-rails'
# cli utils
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
@@ -187,16 +188,18 @@ end
group :test do
gem 'axe-core-capybara'
gem 'axe-core-rspec'
gem "percy-capybara", "~> 5.0.0"
gem 'rails-controller-testing'
gem "rspec-rebound"
gem 'selenium-webdriver'
gem 'timecop'
gem 'vcr'
gem "rspec-rebound"
gem "percy-capybara", "~> 5.0.0"
end
group :travis do
gem 'platform-api'
end
gem "i18n_data", "~> 1.1"

View File

@@ -33,49 +33,47 @@ GEM
GEM
remote: https://rubygems.org/
specs:
actioncable (7.2.3)
actionpack (= 7.2.3)
activesupport (= 7.2.3)
actioncable (7.2.2.2)
actionpack (= 7.2.2.2)
activesupport (= 7.2.2.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.2.3)
actionpack (= 7.2.3)
activejob (= 7.2.3)
activerecord (= 7.2.3)
activestorage (= 7.2.3)
activesupport (= 7.2.3)
actionmailbox (7.2.2.2)
actionpack (= 7.2.2.2)
activejob (= 7.2.2.2)
activerecord (= 7.2.2.2)
activestorage (= 7.2.2.2)
activesupport (= 7.2.2.2)
mail (>= 2.8.0)
actionmailer (7.2.3)
actionpack (= 7.2.3)
actionview (= 7.2.3)
activejob (= 7.2.3)
activesupport (= 7.2.3)
actionmailer (7.2.2.2)
actionpack (= 7.2.2.2)
actionview (= 7.2.2.2)
activejob (= 7.2.2.2)
activesupport (= 7.2.2.2)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.2.3)
actionview (= 7.2.3)
activesupport (= 7.2.3)
cgi
actionpack (7.2.2.2)
actionview (= 7.2.2.2)
activesupport (= 7.2.2.2)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4, < 3.3)
rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (7.2.3)
actionpack (= 7.2.3)
activerecord (= 7.2.3)
activestorage (= 7.2.3)
activesupport (= 7.2.3)
actiontext (7.2.2.2)
actionpack (= 7.2.2.2)
activerecord (= 7.2.2.2)
activestorage (= 7.2.2.2)
activesupport (= 7.2.2.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.2.3)
activesupport (= 7.2.3)
actionview (7.2.2.2)
activesupport (= 7.2.2.2)
builder (~> 3.1)
cgi
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
@@ -84,27 +82,27 @@ GEM
addressable
active_median (0.6.0)
activesupport (>= 7.1)
active_record_union (1.4.0)
activerecord (>= 6.0)
active_utils (3.6.0)
active_record_union (1.3.0)
activerecord (>= 4.0)
active_utils (3.5.0)
activesupport (>= 4.2)
i18n
activejob (7.2.3)
activesupport (= 7.2.3)
activejob (7.2.2.2)
activesupport (= 7.2.2.2)
globalid (>= 0.3.6)
activemodel (7.2.3)
activesupport (= 7.2.3)
activerecord (7.2.3)
activemodel (= 7.2.3)
activesupport (= 7.2.3)
activemodel (7.2.2.2)
activesupport (= 7.2.2.2)
activerecord (7.2.2.2)
activemodel (= 7.2.2.2)
activesupport (= 7.2.2.2)
timeout (>= 0.4.0)
activestorage (7.2.3)
actionpack (= 7.2.3)
activejob (= 7.2.3)
activerecord (= 7.2.3)
activesupport (= 7.2.3)
activestorage (7.2.2.2)
actionpack (= 7.2.2.2)
activejob (= 7.2.2.2)
activerecord (= 7.2.2.2)
activesupport (= 7.2.2.2)
marcel (~> 1.0)
activesupport (7.2.3)
activesupport (7.2.2.2)
base64
benchmark (>= 0.3)
bigdecimal
@@ -121,15 +119,15 @@ GEM
ast (2.4.3)
autoprefixer-rails (10.4.16.0)
execjs (~> 2)
axe-core-api (4.11.0)
axe-core-api (4.10.3)
dumb_delegator
ostruct
virtus
axe-core-capybara (4.11.0)
axe-core-api (= 4.11.0)
axe-core-capybara (4.10.3)
axe-core-api (= 4.10.3)
dumb_delegator
axe-core-rspec (4.11.0)
axe-core-api (= 4.11.0)
axe-core-rspec (4.10.3)
axe-core-api (= 4.10.3)
dumb_delegator
ostruct
virtus
@@ -139,12 +137,12 @@ GEM
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.5.0)
benchmark (0.4.1)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
bigdecimal (3.3.1)
bigdecimal (3.2.2)
bluecloth (2.2.0)
bonsai-elasticsearch-rails (7.0.1)
elasticsearch-model (< 8)
@@ -158,7 +156,7 @@ GEM
actionpack (>= 6.1)
activemodel (>= 6.1)
builder (3.3.0)
bullet (8.1.0)
bullet (8.0.8)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (12.0.0)
@@ -185,8 +183,7 @@ GEM
image_processing (~> 1.1)
marcel (~> 1.0.0)
ssrf_filter (~> 1.0)
cgi (0.5.0)
chartkick (5.2.1)
chartkick (5.2.0)
childprocess (5.0.0)
coderay (1.1.3)
coercible (1.0.0)
@@ -201,7 +198,7 @@ GEM
comfy_bootstrap_form (4.0.9)
rails (>= 5.0.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.5)
connection_pool (2.5.3)
crass (1.0.6)
crowdin-api (1.12.0)
open-uri (>= 0.1.0, < 0.2.0)
@@ -211,7 +208,7 @@ GEM
gli (>= 2.7.0)
i18n (>= 0.6.4)
rubyzip (>= 1.0.0)
csv (3.3.5)
csv (3.3.1)
csv_shaper (1.4.0)
activesupport (>= 3.0.0)
csv
@@ -222,7 +219,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.5.0)
date (3.4.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.9.4)
@@ -254,20 +251,20 @@ GEM
elasticsearch-transport (7.0.0)
faraday
multi_json
erb (6.0.0)
erb (5.0.2)
erubi (1.13.1)
erubis (2.7.0)
excon (1.2.5)
logger
execjs (2.10.0)
factory_bot (6.5.5)
factory_bot (6.5.4)
activesupport (>= 6.1.0)
factory_bot_rails (6.5.1)
factory_bot_rails (6.5.0)
factory_bot (~> 6.5)
railties (>= 6.1.0)
faker (3.5.2)
i18n (>= 1.8.11, < 2)
faraday (2.14.0)
faraday (2.13.4)
faraday-net_http (>= 2.0, < 3.5)
json
logger
@@ -280,7 +277,7 @@ GEM
friendly_id (5.5.1)
activerecord (>= 4.0.0)
gbifrb (0.2.0)
geocoder (1.8.6)
geocoder (1.8.5)
base64 (>= 0.1.0)
csv (>= 3.0.0)
gibbon (1.2.1)
@@ -288,21 +285,27 @@ GEM
multi_json (>= 1.9.0)
gli (2.22.2)
ostruct
globalid (1.3.0)
globalid (1.2.1)
activesupport (>= 6.1)
gravatar-ultimate (2.0.0)
activesupport (>= 2.3.14)
rack
haml (7.0.2)
haml (6.3.0)
temple (>= 0.8.2)
thor
tilt
haml-rails (3.0.0)
haml-i18n-extractor (0.5.9)
activesupport
haml
highline
tilt
trollop (= 1.16.2)
haml-rails (2.1.0)
actionpack (>= 5.1)
activesupport (>= 5.1)
haml (>= 4.0.6)
railties (>= 5.1)
haml_lint (0.67.0)
haml_lint (0.66.0)
haml (>= 5.0)
parallel (~> 1.10)
rainbow
@@ -327,21 +330,20 @@ GEM
multi_xml (>= 0.5.2)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
i18n-tasks (1.1.2)
i18n-tasks (1.0.15)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
erubi
highline (>= 3.0.0)
highline (>= 2.0.0)
i18n
parser (>= 3.2.2.1)
prism
rails-i18n
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.8, >= 1.8.1)
terminal-table (>= 1.5.1)
i18n_data (1.1.0)
simple_po_parser (~> 1.1)
icalendar (2.12.1)
icalendar (2.11.2)
base64
ice_cube (~> 0.16)
logger
@@ -352,18 +354,17 @@ GEM
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
io-console (0.8.1)
irb (1.15.3)
irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jquery-rails (4.6.1)
jquery-rails (4.6.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.16.0)
json-schema (6.0.0)
json (2.13.2)
json-schema (5.1.0)
addressable (~> 2.8)
bigdecimal (~> 3.1)
jsonapi-resources (0.10.7)
activerecord (>= 4.1)
concurrent-ruby
@@ -389,8 +390,7 @@ GEM
loofah (2.24.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.9.0)
logger
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
@@ -417,7 +417,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (5.26.2)
minitest (5.25.5)
moneta (1.0.0)
msgpack (1.8.0)
multi_json (1.15.0)
@@ -425,7 +425,7 @@ GEM
bigdecimal (~> 3.1)
net-http (0.6.0)
uri
net-imap (0.5.12)
net-imap (0.5.9)
date
net-protocol
net-pop (0.1.2)
@@ -435,14 +435,14 @@ GEM
net-smtp (0.5.1)
net-protocol
netrc (0.11.0)
nio4r (2.7.5)
nokogiri (1.18.10)
nio4r (2.7.4)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.10-x86_64-linux-gnu)
nokogiri (1.18.9-x86_64-linux-gnu)
racc (~> 1.4)
oauth (0.5.6)
oj (3.16.12)
oj (3.16.11)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
omniauth (1.9.2)
@@ -458,34 +458,34 @@ GEM
orm_adapter (0.5.0)
ostruct (0.6.3)
parallel (1.27.0)
parser (3.3.10.0)
parser (3.3.9.0)
ast (~> 2.4.1)
racc
percy-capybara (5.0.0)
capybara (>= 3)
pg (1.6.2)
pg (1.6.2-x86_64-linux)
pg (1.6.1)
pg (1.6.1-x86_64-linux)
platform-api (3.8.0)
heroics (~> 0.1.1)
moneta (~> 1.0.0)
rate_throttle_client (~> 0.1.0)
popper_js (2.11.8)
pp (0.6.3)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
prism (1.6.0)
prism (1.4.0)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
psych (5.2.6)
date
stringio
public_suffix (6.0.2)
puma (7.1.0)
public_suffix (6.0.1)
puma (6.6.1)
nio4r (~> 2.0)
query_diet (0.7.3)
query_diet (0.7.2)
racc (1.8.1)
rack (2.2.21)
rack (2.2.17)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-protection (3.2.0)
@@ -498,20 +498,20 @@ GEM
rackup (1.0.1)
rack (< 3)
webrick
rails (7.2.3)
actioncable (= 7.2.3)
actionmailbox (= 7.2.3)
actionmailer (= 7.2.3)
actionpack (= 7.2.3)
actiontext (= 7.2.3)
actionview (= 7.2.3)
activejob (= 7.2.3)
activemodel (= 7.2.3)
activerecord (= 7.2.3)
activestorage (= 7.2.3)
activesupport (= 7.2.3)
rails (7.2.2.2)
actioncable (= 7.2.2.2)
actionmailbox (= 7.2.2.2)
actionmailer (= 7.2.2.2)
actionpack (= 7.2.2.2)
actiontext (= 7.2.2.2)
actionview (= 7.2.2.2)
activejob (= 7.2.2.2)
activemodel (= 7.2.2.2)
activerecord (= 7.2.2.2)
activestorage (= 7.2.2.2)
activesupport (= 7.2.2.2)
bundler (>= 1.15.0)
railties (= 7.2.3)
railties (= 7.2.2.2)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -531,42 +531,39 @@ GEM
rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (7.2.3)
actionpack (= 7.2.3)
activesupport (= 7.2.3)
cgi
railties (7.2.2.2)
actionpack (= 7.2.2.2)
activesupport (= 7.2.2.2)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
raindrops (0.20.1)
rake (13.3.1)
rake (13.3.0)
rate_throttle_client (0.1.2)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rdoc (6.16.1)
rdoc (6.14.2)
erb
psych (>= 4.0.0)
tsort
recaptcha (5.21.1)
recaptcha (5.20.1)
redis-client (0.23.2)
connection_pool
regexp_parser (2.11.3)
reline (0.6.3)
regexp_parser (2.11.2)
reline (0.6.2)
io-console (~> 0.5)
responders (3.2.0)
actionpack (>= 7.0)
railties (>= 7.0)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rexml (3.4.4)
rexml (3.4.1)
rouge (4.1.2)
rspec (3.13.0)
rspec-core (~> 3.13.0)
@@ -576,7 +573,7 @@ GEM
activemodel (>= 3.0)
activesupport (>= 3.0)
rspec-mocks (>= 2.99, < 4.0)
rspec-core (3.13.6)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
@@ -594,23 +591,23 @@ GEM
rspec-support (~> 3.13)
rspec-rebound (0.2.1)
rspec-core (~> 3.3)
rspec-support (3.13.6)
rspec-support (3.13.4)
rspectre (0.2.0)
parser (>= 3.3.7.1)
prism (~> 1.3)
rspec (~> 3.10)
rswag-api (2.17.0)
activesupport (>= 5.2, < 8.2)
railties (>= 5.2, < 8.2)
rswag-specs (2.17.0)
activesupport (>= 5.2, < 8.2)
json-schema (>= 2.2, < 7.0)
railties (>= 5.2, < 8.2)
rswag-api (2.16.0)
activesupport (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rswag-specs (2.16.0)
activesupport (>= 5.2, < 8.1)
json-schema (>= 2.2, < 6.0)
railties (>= 5.2, < 8.1)
rspec-core (>= 2.14)
rswag-ui (2.17.0)
actionpack (>= 5.2, < 8.2)
railties (>= 5.2, < 8.2)
rubocop (1.81.7)
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rubocop (1.80.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -618,19 +615,19 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.47.1, < 2.0)
rubocop-ast (>= 1.46.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.48.0)
rubocop-ast (1.46.0)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-factory_bot (2.28.0)
rubocop-factory_bot (2.27.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rails (2.34.1)
rubocop-rails (2.33.3)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
@@ -639,10 +636,10 @@ GEM
rubocop-rake (0.7.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
rubocop-rspec (3.8.0)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.81)
rubocop-rspec_rails (2.32.0)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
@@ -650,7 +647,7 @@ GEM
ruby-units (4.1.0)
ruby-vips (2.2.1)
ffi (~> 1.12)
rubyzip (3.2.1)
rubyzip (3.0.1)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@@ -664,13 +661,13 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
scout_apm (5.8.0)
scout_apm (5.7.1)
parser
searchkick (5.3.1)
activemodel (>= 6.1)
hashie
securerandom (0.4.1)
selenium-webdriver (4.38.0)
selenium-webdriver (4.35.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
@@ -692,28 +689,28 @@ GEM
activesupport (>= 5.2)
sprockets (>= 3.0.0)
ssrf_filter (1.1.2)
stringio (3.1.8)
stringio (3.1.7)
sysexits (1.2.0)
temple (0.10.4)
terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4)
terser (1.2.6)
terser (1.2.5)
execjs (>= 0.3.0, < 3)
thor (1.4.0)
thread_safe (0.3.6)
tilt (2.6.1)
timecop (0.9.10)
timeout (0.4.4)
tsort (0.2.0)
timeout (0.4.3)
trollop (1.16.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.1.0)
unicode-display_width (3.1.5)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unicorn (6.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.18.0)
uniform_notifier (1.17.0)
uri (1.0.3)
useragent (0.16.11)
validate_url (1.0.15)
@@ -731,7 +728,7 @@ GEM
nokogiri (>= 1.2.0)
rack (>= 1.0)
rack-test (>= 0.5.3)
webrick (1.9.2)
webrick (1.9.1)
websocket (1.2.11)
websocket-driver (0.8.0)
base64
@@ -791,6 +788,7 @@ DEPENDENCIES
gibbon (~> 1.2.0)
gravatar-ultimate
haml
haml-i18n-extractor
haml-rails
haml_lint (>= 0.25.1)
hashie (>= 3.5.3)

View File

@@ -17,9 +17,7 @@ encourage participation from people of all backgrounds and skill levels.
## Want to contribute?
Don't ask to ask, the best way to get started is to fork the project, start a codespace and get hacking.
Dive on in and submit your PRs!
Vibe Coding is more than okay, just make sure you indicate if you have done so and ensure there are tests.
Dive on in and submit your PRs.
## Important links
@@ -37,10 +35,6 @@ frontend features. We welcome contributions -- see
* To set up your development environment, see [Getting started](https://github.com/Growstuff/growstuff/wiki/New-contributor-guide).
* You may also be interested in our [API](https://github.com/Growstuff/growstuff/wiki/API).
### For Home Automation enthusiasts
https://github.com/Growstuff/homeassistant-growstuff/
## For designers, writers, researchers, data wranglers, and other contributors
There are heaps of ways to get involved and contribute no matter what

View File

@@ -1,13 +1,7 @@
.crop-icon {
height: 1em;
}
.card-footer {
.btn-group-vertical {
.btn {
text-wrap: initial
}
}
}
.crop-thumbnail {
.text {
bottom: 0;

View File

@@ -1,8 +1,6 @@
// stats shown on homepage. eg. "999 members..."
.stats {
a {
font-weight: bold;
}
font-weight: bold;
}
.crops,
@@ -16,27 +14,3 @@
.homepage--list-item {
height: 100px;
}
.releases {
.card {
.card-header {
}
.card-body {
h2 {
background-color: transparent;
color: black;
box-shadow: none;
}
img {
border: 0.5em solid #111;
margin-left: 5%;
margin-right: 5%;
width: 90%;
}
ul {
margin-bottom: 1em;
margin-top: 1em;
}
}
}
}

View File

@@ -10,33 +10,9 @@
width: 100%;
}
#navbarSupportedContent {
ul {
flex-direction: column-reverse;
flex-wrap: nowrap;
li.nav-item {
display: block;
a {
display: grid;
grid-template-columns: 2em 1fr 2em;
}
a.dropdown-toggle::after {
width: 100%;
text-align: right;
}
}
}
}
.crop-actions {
flex-direction: column;
width: 100%;
a {
margin: auto;
.navbar .nav > li {
display: block;
}
}
.navbar .navbar-form {
padding-left: 0;

View File

@@ -10,7 +10,6 @@ body {
.navbar {
flex-wrap: nowrap;
align-items: flex-start
}
.navbar-brand {
.site-name {
@@ -132,8 +131,6 @@ section {
border-radius: 5%;
margin: 0.5em 0.5em 0.5em 0;
width: 200px;
align-items: stretch;
justify-content: space-between;
.img-card {
border-top-left-radius: 5%;
@@ -370,6 +367,9 @@ ul.thumbnail-buttons {
h1 {
font-size: 400%;
}
.stats a {
color: $black;
}
// signup widget on homepage
.signup {

View File

@@ -24,29 +24,14 @@ class ActivitiesController < DataController
end
def show
if @activity.finished? && @activity.owner == current_member && (@activity.updated_at + 2.weeks) > Time.now
@repeat_link = new_activity_path(
name: @activity.name,
garden_id: @activity.garden_id,
planting_id: @activity.planting_id,
category: @activity.category,
description: @activity.description,
due_date: 2.weeks.from_now.to_date
)
end
respond_with @activity
end
def new
@activity = Activity.new(
owner: current_member,
owner: current_member,
due_date: Date.today
)
@activity.name = params[:name] if params[:name]
@activity.description = params[:description] if params[:description]
@activity.category = params[:category] if params[:category]
@activity.due_date = params[:due_date] if params[:due_date]
if params[:garden_id]
@activity.garden = Garden.find_by(
owner: current_member,
@@ -73,21 +58,7 @@ class ActivitiesController < DataController
def create
@activity = Activity.new(activity_params)
@activity.owner = current_member
@activity.due_date ||= Date.today
if @activity.save
if params[:repeat_times].to_i > 0
repeat_times = params[:repeat_times].to_i
repeat_weeks = params[:repeat_weeks].to_i
repeat_times.times do |i|
new_activity = @activity.dup
new_activity.due_date = @activity.due_date + (i + 1) * repeat_weeks.weeks
new_activity.save
end
end
end
@activity.save
respond_with @activity
end
@@ -106,8 +77,7 @@ class ActivitiesController < DataController
def activity_params
params.require(:activity).permit(
:name, :description, :category, :finished,
:garden_id, :planting_id, :due_date,
:repeat_times, :repeat_weeks
:garden_id, :planting_id, :due_date
)
end

View File

@@ -1,40 +0,0 @@
# frozen_string_literal: true
module Admin
class CropCompanionsController < AdminController
before_action :set_crop
def index
@crop_companions = @crop.crop_companions
end
def new
@crop_companion = @crop.crop_companions.new
end
def create
@crop_companion = @crop.crop_companions.new(crop_companion_params)
if @crop_companion.save
redirect_to admin_crop_crop_companions_path(@crop), notice: 'Companion was successfully created.'
else
render :new
end
end
def destroy
@crop_companion = @crop.crop_companions.find(params[:id])
@crop_companion.destroy
redirect_to admin_crop_crop_companions_path(@crop), notice: 'Companion was successfully destroyed.'
end
private
def set_crop
@crop = Crop.find_by!(slug: params[:crop_slug])
end
def crop_companion_params
params.require(:crop_companion).permit(:crop_b_id, :source_url)
end
end
end

View File

@@ -1,8 +0,0 @@
# frozen_string_literal: true
module Api
module V1
class ActivitiesController < BaseController
end
end
end

View File

@@ -4,40 +4,6 @@ module Api
module V1
class BaseController < JSONAPI::ResourceController
abstract
protect_from_forgery with: :null_session
before_action :authenticate_member_from_token!
before_action :enforce_member_for_write_operations!, only: %i(create update destroy)
rescue_from CanCan::AccessDenied do
head :forbidden
end
def context
{
current_user: current_user,
current_ability: current_ability,
controller: self,
action: params[:action]
}
end
private
attr_reader :current_user
def enforce_member_for_write_operations!
head :unauthorized unless current_user
end
def authenticate_member_from_token!
authenticate_with_http_token do |token, _options|
auth = Authentication.find_by(token: token, provider: 'api')
if auth.present?
@current_user = auth.member
return true
end
end
end
end
end
end

View File

@@ -188,13 +188,10 @@ class CropsController < ApplicationController
def crop_params
params.require(:crop).permit(
:name, :en_wikipedia_url, :en_youtube_url,
:name, :en_wikipedia_url,
:parent_id, :perennial,
:request_notes, :reason_for_rejection,
:rejection_notes,
:description,
:row_spacing, :spread, :height,
:sowing_method, :sun_requirements, :growing_degree_days,
scientific_names_attributes: %i(scientific_name _destroy id)
)
end

View File

@@ -4,7 +4,7 @@ class GardensController < DataController
def index
@owner = Member.find_by(slug: params[:member_slug])
@show_all = params[:all] == '1'
@show_jump_to = params[:member_slug].present? || false
@show_jump_to = params[:member_slug].present? ? true : false
@gardens = @gardens.includes(:owner)
@gardens = @gardens.active unless @show_all
@@ -18,9 +18,8 @@ class GardensController < DataController
end
def show
@current_plantings = @garden.plantings.current.where.not(failed: true).includes(:crop, :owner).order(planted_at: :desc)
@current_plantings = @garden.plantings.current.includes(:crop, :owner).order(planted_at: :desc)
@current_activities = @garden.activities.current.includes(:owner).order(created_at: :desc)
@finished_activities = @garden.activities.finished.includes(:owner).order(created_at: :desc)
@finished_plantings = @garden.plantings.finished.includes(:crop)
@suggested_companions = Crop.approved.where(
id: CropCompanion.where(crop_a_id: @current_plantings.select(:crop_id)).select(:crop_b_id)
@@ -39,10 +38,7 @@ class GardensController < DataController
def create
@garden.owner_id = current_member.id
if @garden.save
link = new_activity_path(name: 'Weed the garden bed', garden_id: @garden.id, due_date: 2.weeks.from_now.to_date)
flash[:notice] = t('gardens.created_prompt_html', link: link).html_safe
end
flash[:notice] = I18n.t('gardens.created') if @garden.save
respond_with(@garden)
end

View File

@@ -37,7 +37,6 @@ class PlantingsController < DataController
@photos = @planting.photos.includes(:owner).order(date_taken: :desc)
@harvests = Harvest.search(where: { planting_id: @planting.id })
@current_activities = @planting.activities.current.includes(:owner).order(created_at: :desc)
@finished_activities = @planting.activities.finished.includes(:owner).order(created_at: :desc)
@matching_seeds = matching_seeds
@crop = @planting.crop
@@ -46,12 +45,6 @@ class PlantingsController < DataController
.where.not(id: @planting.id)
.includes(:owner, :crop, :garden)
.limit(6)
if @planting.finished? && @planting.garden.plantings.current.none? && (@planting.updated_at + 2.weeks) > Time.zone.now
@cultivate_soil_link = new_activity_path(name: 'Cultivate soil', garden_id: @planting.garden_id, category: "Soil Cultivation",
description: "Recently finished #{@planting.crop.name} planting. Prepare for next planting.")
end
respond_with @planting
end
@@ -140,7 +133,7 @@ class PlantingsController < DataController
:crop_id, :description, :garden_id, :planted_at,
:parent_seed_id,
:quantity, :sunniness, :planted_from, :finished,
:finished_at, :failed, :overall_rating
:finished_at, :failed
)
end

View File

@@ -6,7 +6,7 @@ class RegistrationsController < Devise::RegistrationsController
prepend_before_action :check_captcha, only: [:create] # Change this to be any actions you want to protect with recaptcha.
def edit
@flickr_auth = current_member.auth('flickr')
@flickr_auth = current_member.auth('flickr')
render "edit"
end
@@ -38,12 +38,6 @@ class RegistrationsController < Devise::RegistrationsController
end
end
def regenerate_api_token
current_member.regenerate_api_token
set_flash_message :notice, :api_token_regenerated
redirect_to edit_member_registration_path + '#apps'
end
def destroy
if @member.valid_password?(params.require(:member)[:current_password])
@member.discard

View File

@@ -19,7 +19,9 @@ class SeedsController < DataController
where['parent_planting'] = @planting.id
end
where['tradeable_to'] = params[:tradeable_to] if params[:tradeable_to].present?
if params[:tradeable_to].present?
where['tradeable_to'] = params[:tradeable_to]
end
@show_all = (params[:all] == '1')
where['finished'] = false unless @show_all
@@ -43,7 +45,6 @@ class SeedsController < DataController
def new
@seed = Seed.new
@seed.source = 'my own seed saving'
if params[:planting_slug]
@planting = Planting.find_by(slug: params[:planting_slug])
@@ -57,8 +58,6 @@ class SeedsController < DataController
def create
@seed = Seed.new(seed_params)
@seed.source ||= 'my own seed saving'
@seed.finished ||= false
@seed.owner = current_member
@seed.crop = @seed.parent_planting.crop if @seed.parent_planting
flash[:notice] = "Successfully added #{@seed.crop} seed to your stash." if @seed.save
@@ -86,7 +85,7 @@ class SeedsController < DataController
:crop_id, :description, :quantity, :plant_before,
:parent_planting_id, :saved_at,
:days_until_maturity_min, :days_until_maturity_max,
:organic, :gmo, :source,
:organic, :gmo,
:heirloom, :tradable_to, :slug,
:finished, :finished_at
)

View File

@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'nokogiri'
module ApplicationHelper
def parse_date(str)
str ||= '' # Date.parse barfs on nil
@@ -22,32 +21,6 @@ module ApplicationHelper
classes
end
# Similar to Rails' time_ago_in_words, but gives a more standard
# output like "in 3 days" or "5 months ago".
# Also handles the case where from_time is a Date and to_time is a Date
# (in which case it just says "today" if they're the same date).
#
# NOTE: This is similar to distance_of_time_in_words but different enough
# that I think it's worth having a separate helper for it.
#
# from_time - the starting time (Time or Date)
# to_time - the ending time (Time or Date). Default: now (Time.zone.now)
# include_seconds - whether to include seconds in the calculation
#
# Returns a string like "in 3 days" or "5 months ago"
def standard_time_distance(from_time, to_time = 0, include_seconds = false)
return 'today' if from_time.is_a?(Date) && (from_time == to_time)
return 'now' if from_time == to_time
return "#{distance_of_time_in_words(from_time, to_time, include_seconds:)} ago" if from_time < to_time
"in #{distance_of_time_in_words(from_time, to_time, include_seconds:)}"
end
def count_github_contibutors
File.open(Rails.root.join('CONTRIBUTORS.md')).readlines.grep(/^-/).size
end
# Produces a cache key for uniquely identifying cached fragments.
def cache_key_for(klass, identifier = "all")
count = klass.count
@@ -120,22 +93,4 @@ module ApplicationHelper
def og_description(description)
strip_tags(description).split(' ')[0..20].join(' ')
end
def github_releases
return [] if Rails.env.test?
feed_url = 'https://github.com/Growstuff/growstuff/releases.atom'
Rails.cache.fetch(feed_url, expires_in: 1.day) do
response = Faraday.get(feed_url)
doc = Nokogiri::XML(response.body)
doc.xpath('//xmlns:entry').first(2).map do |entry|
{
title: entry.xpath('xmlns:title').text,
content: entry.xpath('xmlns:content').text,
link: entry.xpath('xmlns:link/@href').text,
updated: entry.xpath('xmlns:updated').text
}
end
end
end
end

View File

@@ -13,7 +13,7 @@ module AutoSuggestHelper
resource = resource.class.name.downcase
source_path = Rails.application.routes.url_helpers.send("search_#{source}s_path", format: :json)
%(
<input id="#{source}" class="auto-suggest #{options[:class]}" #{'required="required"' if options[:required]}
<input id="#{source}" class="auto-suggest #{options[:class]}" #{options[:required] ? 'required="required"' : ''}
type="text" value="#{default}" data-source-url="#{source_path}",
placeholder="e.g. lettuce">
<noscript class="text-warning">

View File

@@ -53,7 +53,7 @@ module ButtonsHelper
link_to t('buttons.mark_as_inactive'),
garden_path(garden, garden: { active: 0 }),
method: :put, class: classes,
data: { confirm: I18n.t('gardens.confirm_deactivate') }
data: { confirm: 'All plantings associated with this garden will be marked as finished. Are you sure?' }
end
def create_button(model_to_create, path, icon, label)
@@ -85,20 +85,7 @@ module ButtonsHelper
end
def activity_edit_button(activity, classes: "btn btn-raised btn-info")
edit_button(edit_activity_path(slug: activity.slug), classes:)
end
def activity_copy_button(activity, classes: 'btn')
link_to new_activity_path(
name: activity.name,
description: activity.description,
category: activity.category,
garden_id: activity.garden_id,
planting_id: activity.planting_id,
due_date: activity.due_date
), class: classes do
copy_icon + ' ' + t('buttons.copy')
end
edit_button(edit_activity_path(activity), classes:)
end
def activity_finish_button(activity, classes: 'btn btn-default btn-secondary')

View File

@@ -17,12 +17,4 @@ module CropsHelper
def crop_ebay_seeds_url(crop)
"https://www.ebay.com/sch/i.html?_nkw=#{CGI.escape crop.name}"
end
def youtube_video_id(url)
return unless url
regex = %r{(?:youtube(?:-nocookie)?\.com/(?:[^/\n\s]+/\S+/|(?:v|e(?:mbed)?)/|\S*?[?&]v=)|youtu\.be/)([a-zA-Z0-9_-]{11})}
match = url.match(regex)
match[1] if match
end
end

View File

@@ -7,8 +7,6 @@ module EventHelper
def event_description(event)
render "#{event.event_type.pluralize}/description", event_model: resolve_model(event)
rescue ActionView::MissingTemplate
"#{event.event_type.humanize.downcase}d"
end
def resolve_model(event)

View File

@@ -59,10 +59,6 @@ module IconsHelper
image_icon 'delete'
end
def copy_icon
icon('far', 'copy')
end
def add_photo_icon
image_icon 'add-photo'
end

View File

@@ -76,7 +76,6 @@ class Ability
if member.role? :crop_wrangler
can :wrangle, Crop
can :manage, Crop
can :manage, CropCompanion
can :manage, ScientificName
can :manage, AlternateName
can :openfarm, Crop

View File

@@ -30,20 +30,4 @@ class Activity < ApplicationRecord
def to_s
name
end
def garden_name
garden&.name
end
def garden_slug
garden&.slug
end
def planting_name
planting&.crop&.name
end
def planting_slug
planting&.crop&.slug
end
end

View File

@@ -8,6 +8,14 @@ module OpenFarmData
fetch_attr('main_image_path')
end
def height
fetch_attr('height')
end
def spread
fetch_attr('spread')
end
def svg_icon
icon = fetch_attr('svg_icon')
return icon if icon.present?
@@ -19,18 +27,42 @@ module OpenFarmData
fetch_attr('tags_array')
end
def description
fetch_attr('description')
end
def row_spacing
fetch_attr('row_spacing')
end
def common_names
fetch_attr('common_names')
end
def guides_count
fetch_attr('guides_count')
end
def binomial_name
fetch_attr('binomial_name')
end
def sowing_method
fetch_attr('sowing_method')
end
def main_image_path
fetch_attr('main_image_path')
end
def sun_requirements
fetch_attr('sun_requirements')
end
def growing_degree_days
fetch_attr('growing_degree_days')
end
def processing_pictures
fetch_attr('processing_pictures')
end
@@ -39,6 +71,6 @@ module OpenFarmData
def fetch_attr(key)
return if openfarm_data.blank?
openfarm_data.dig('attributes', key)
openfarm_data.fetch('attributes', {}).fetch(key, nil)
end
end

View File

@@ -94,9 +94,9 @@ module PredictPlanting
private
def calculate_percentage_grown
return 0 if age_in_days.to_i < 0
return 0 if age_in_days < 0
percent = (age_in_days.to_f / expected_lifespan.to_f) * 100
percent = (age_in_days / expected_lifespan.to_f) * 100
(percent > 100 ? 100 : percent)
end
end

View File

@@ -9,9 +9,7 @@ module SearchActivities
mappings: {
properties: {
active: { type: :boolean },
created_at: { type: :integer },
updated_at: { type: :integer },
due_date: { type: :date }
created_at: { type: :integer }
}
}
@@ -25,10 +23,8 @@ module SearchActivities
category:,
garden_id:,
garden_name: garden&.name,
garden_slug: garden&.garden_slug,
planting_id:,
planting_name: planting&.crop&.name,
planting_slug: planting&.slug,
description:,
# owner

View File

@@ -59,8 +59,7 @@ module SearchSeeds
search('*', limit:,
where: {
finished: false,
tradable: true,
_or: [{ plant_before: nil }, { plant_before: { lt: Date.today } }]
tradable: true
},
boost_by: [:created_at],
load: false)

View File

@@ -55,12 +55,6 @@ class Crop < ApplicationRecord
message: 'is not a valid English Wikipedia URL'
},
if: :approved?
validates :en_youtube_url,
format: {
with: %r{\A(?:https?://)?(?:www\.)?(?:youtube(?:-nocookie)?\.com/(?:(?:v|e(?:mbed)?)/|\S*?[?&]v=)|youtu\.be/)[a-zA-Z0-9_-]{11}(?:[?&]\S*)?\z},
message: 'is not a valid YouTube URL'
},
allow_blank: true
validates :name, uniqueness: { scope: :approval_status }, if: :pending?
def to_s
@@ -96,7 +90,7 @@ class Crop < ApplicationRecord
def popular_plant_parts
PlantPart.joins(:harvests)
.where("crop_id = ?", id)
.order(count_harvests_id: :desc)
.order("count_harvests_id DESC")
.group("plant_parts.id", "plant_parts.name")
.count("harvests.id")
end
@@ -159,20 +153,8 @@ class Crop < ApplicationRecord
where(["lower(crops.name) = :value", { value: name.downcase }])
end
def all_companions
return companions unless parent
(companions + parent.companions).uniq
end
before_destroy :destroy_reverse_companionships
private
def destroy_reverse_companionships
CropCompanion.where(crop_b: self).destroy_all
end
def count_uses_of_property(col_name)
plantings.unscoped
.where(crop_id: id)

View File

@@ -3,7 +3,6 @@
class Forum < ApplicationRecord
extend FriendlyId
include Ownable
validates :name, presence: true
validates :description, presence: true
friendly_id :name, use: %i(slugged finders)

View File

@@ -2,7 +2,6 @@
class GardenType < ApplicationRecord
extend FriendlyId
friendly_id :name, use: %i(slugged finders)
has_many :gardens, dependent: :nullify

View File

@@ -2,14 +2,12 @@
class Member < ApplicationRecord
include Discard::Model
acts_as_messageable # messages can be sent to this model
include Geocodable
include MemberFlickr
include MemberNewsletter
extend FriendlyId
friendly_id :login_name, use: %i(slugged finders)
#
@@ -26,20 +24,6 @@ class Member < ApplicationRecord
has_many :notifications, foreign_key: 'recipient_id', inverse_of: :recipient
has_many :sent_notifications, foreign_key: 'sender_id', inverse_of: :sender, class_name: "Notification"
has_many :authentications, dependent: :destroy
has_one :api_token, -> { where(provider: 'api') }, class_name: 'Authentication', dependent: :destroy
def api_token?
api_token.present?
end
def regenerate_api_token
api_token.destroy if api_token?
create_api_token(
provider: 'api',
uid: id,
token: SecureRandom.hex(16)
)
end
has_many :photos, inverse_of: :owner
has_many :likes, dependent: :destroy
@@ -107,10 +91,9 @@ class Member < ApplicationRecord
uniqueness: {
case_sensitive: false
}
validates :website_url, format: { with: %r{\Ahttps?://}, message: "must start with http:// or https://" }, allow_blank: true
validates :other_url, format: { with: %r{\Ahttps?://}, message: "must start with http:// or https://" }, allow_blank: true
validates :instagram_handle, :facebook_handle, :bluesky_handle,
format: { without: %r{\Ahttps?://|/}, message: "should be a handle, not a URL" }, allow_blank: true
validates :website_url, format: { with: /\Ahttps?:\/\//, message: "must start with http:// or https://" }, allow_blank: true
validates :other_url, format: { with: /\Ahttps?:\/\//, message: "must start with http:// or https://" }, allow_blank: true
validates :instagram_handle, :facebook_handle, :bluesky_handle, format: { without: %r{\Ahttps?:\/\/|\/}, message: "should be a handle, not a URL" }, allow_blank: true
#
# Triggers

View File

@@ -46,8 +46,7 @@ class Photo < ApplicationRecord
flickr = owner.flickr
info = flickr.photos.getInfo(photo_id: source_id)
licenses = flickr.photos.licenses.getInfo
license = licenses.find { |l| l.id.to_i == info.license.to_i }
Rails.logger.error("Cannot find license: #{[info.license, licenses].inspect}") unless license
license = licenses.find { |l| l.id == info.license }
{
title: calculate_title(info),
license_name: license.name,

View File

@@ -2,7 +2,6 @@
class PlantPart < ApplicationRecord
extend FriendlyId
friendly_id :name, use: %i(slugged finders)
has_many :harvests, dependent: :destroy

View File

@@ -25,8 +25,6 @@ class Planting < ApplicationRecord
has_many :harvests, dependent: :destroy
has_many :activities, dependent: :destroy
scope :current, -> { where.not(finished: true).where.not(failed: true) }
#
# Ancestry of food
belongs_to :parent_seed, class_name: 'Seed', # parent,
@@ -85,9 +83,6 @@ class Planting < ApplicationRecord
validates :planted_from, allow_blank: true, inclusion: {
in: PLANTED_FROM_VALUES, message: "%<value>s is not a valid planting method"
}
validates :overall_rating, allow_blank: true, numericality: {
only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5
}
def planting_slug
[

View File

@@ -2,7 +2,6 @@
class Role < ApplicationRecord
extend FriendlyId
friendly_id :name, use: %i(slugged finders)
validates :name, uniqueness: true, presence: true

View File

@@ -6,15 +6,12 @@ class Seed < ApplicationRecord
include Finishable
include Ownable
include SearchSeeds
friendly_id :seed_slug, use: %i(slugged finders)
TRADABLE_TO_VALUES = %w(nowhere locally nationally internationally).freeze
ORGANIC_VALUES = ['certified organic', 'non-certified organic', 'conventional/non-organic', 'unknown'].freeze
GMO_VALUES = ['certified GMO-free', 'non-certified GMO-free', 'GMO', 'unknown'].freeze
HEIRLOOM_VALUES = %w(heirloom hybrid unknown).freeze
SOURCE_VALUES = ['seed catalogue', 'retail outlet', 'seed bank or similar institution',
'traded from another person', 'my own seed saving', 'other/unknown'].freeze
#
# Relationships
@@ -47,9 +44,6 @@ class Seed < ApplicationRecord
validates :heirloom, allow_blank: false,
inclusion: { in: HEIRLOOM_VALUES, message: "You must say whether the seeds" \
"are heirloom, hybrid, or unknown" }
validates :source, allow_blank: true,
inclusion: { in: SOURCE_VALUES, message: "You must say where the seeds are from," \
"or that you don't know" }
#
# Delegations

View File

@@ -1,29 +0,0 @@
# frozen_string_literal: true
module Api
module V1
class ActivityResource < BaseResource
before_create do
@model.owner = context[:current_user]
end
has_one :owner, class_name: 'Member', always_include_linkage_data: true
has_one :garden, always_include_linkage_data: true
has_one :planting, always_include_linkage_data: true
attribute :name
attribute :description
attribute :category
attribute :finished
attribute :due_date
filter :owner
filter :owner_id
filter :garden
filter :garden_id
filter :planting
filter :planting_id
filter :category
end
end
end

View File

@@ -3,7 +3,8 @@
module Api
module V1
class CropResource < BaseResource
immutable # TODO: Re-evaluate this later
immutable
filter :approval_status, default: 'approved'
has_many :plantings
@@ -12,7 +13,7 @@ module Api
has_many :photos
has_one :parent, class_name: 'Crop', always_include_linkage_data: true
has_one :parent, class_name: 'Crop'
attribute :name
attribute :en_wikipedia_url

View File

@@ -3,22 +3,13 @@
module Api
module V1
class GardenResource < BaseResource
before_create do
@model.owner = context[:current_user]
end
immutable
has_one :owner, class_name: 'Member', always_include_linkage_data: true
has_one :owner, class_name: 'Member'
has_many :plantings
has_many :photos
attribute :name
filter :owner
filter :owner_id
filter :active
filter :garden_type
filter :location
filter :slug
end
end
end

View File

@@ -3,17 +3,11 @@
module Api
module V1
class HarvestResource < BaseResource
before_save do
@model.owner = context[:current_user]
@model.crop_id = @model.planting.crop_id if @model.planting_id
@model.harvested_at = Time.zone.now if @model.harvested_at.blank?
@model.plant_part = PlantPart.first
end
immutable
has_one :crop, always_include_linkage_data: true
has_one :planting, always_include_linkage_data: true
has_one :owner, class_name: 'Member', always_include_linkage_data: true
# has_one :plant_part
has_one :crop
has_one :planting
has_one :owner, class_name: 'Member'
has_many :photos
attribute :harvested_at
@@ -22,15 +16,6 @@ module Api
attribute :weight_quantity
attribute :weight_unit
attribute :si_weight
filter :owner
filter :owner_id
filter :crop
filter :crop_id
filter :planting
filter :planting_id
filter :plant_part
filter :harvested_at
end
end
end

View File

@@ -9,7 +9,6 @@ module Api
has_many :plantings, foreign_key: 'owner_id'
has_many :harvests, foreign_key: 'owner_id'
has_many :seeds, foreign_key: 'owner_id'
has_many :activities, foreign_key: 'owner_id'
has_many :photos

View File

@@ -3,12 +3,9 @@
module Api
module V1
class PhotoResource < BaseResource
immutable # TODO: Re-evaluate this.
before_create do
@model.owner = context[:current_user]
end
immutable
has_one :owner, class_name: 'Member', always_include_linkage_data: true
has_one :owner, class_name: 'Member'
has_many :plantings
has_many :gardens
has_many :harvests

View File

@@ -3,13 +3,11 @@
module Api
module V1
class PlantingResource < BaseResource
before_create do
@model.owner = context[:current_user]
end
immutable
has_one :garden, always_include_linkage_data: true
has_one :crop, always_include_linkage_data: true
has_one :owner, class_name: 'Member', always_include_linkage_data: true
has_one :garden
has_one :crop
has_one :owner, class_name: 'Member'
has_many :photos
has_many :harvests
@@ -38,10 +36,6 @@ module Api
filter :owner
filter :owner_id
filter :finished
filter :active, apply: ->(records, _value, _options) { records.active }
filter :failed, apply: ->(records, _value, _options) { records.failed }
filter :sunniness
filter :perennial, apply: ->(records, _value, _options) { records.perennial }
attribute :percentage_grown
delegate :percentage_grown, to: :@model

View File

@@ -3,12 +3,10 @@
module Api
module V1
class SeedResource < BaseResource
before_create do
@model.owner = context[:current_user]
end
immutable
has_one :owner, class_name: 'Member', always_include_linkage_data: true
has_one :crop, always_include_linkage_data: true
has_one :owner, class_name: 'Member'
has_one :crop
attribute :description
attribute :quantity
@@ -19,15 +17,6 @@ module Api
attribute :organic
attribute :gmo
attribute :heirloom
filter :owner
filter :owner_id
filter :crop
filter :crop_id
filter :tradable_to
filter :organic
filter :gmo
filter :heirloom
end
end
end

View File

@@ -1,16 +1,6 @@
# frozen_string_literal: true
class BaseResource < JSONAPI::Resource
immutable
abstract
%i(create update remove).each do |action|
set_callback action, :before, :authorize
end
# Check authorisation for write operations.
# NOTE: At a later time, we may require API tokens for READ operations.
def authorize
# context[:action] is simply context[:controller].params[:action]
context[:current_ability].authorize! context[:action].to_sym, @model
end
end

View File

@@ -0,0 +1,108 @@
# frozen_string_literal: true
BASE = 'https://openfarm.cc/api/v1/'
# BASE = 'http://127.0.0.1:3000/api/v1/'
class OpenfarmService
def initialize
@cropbot = Member.find_by(login_name: 'cropbot')
end
def import!
Crop.all.order(updated_at: :desc).each do |crop|
Rails.logger.debug { "#{crop.id}, #{crop.name}" }
update_crop(crop) if crop.valid?
end
end
def update_crop(crop)
openfarm_record = fetch(crop.name)
if openfarm_record.present? && openfarm_record.is_a?(String)
Rails.logger.info(openfarm_record)
elsif openfarm_record.present? && openfarm_record.fetch('data', false)
crop.update! openfarm_data: openfarm_record.fetch('data', false)
save_companions(crop, openfarm_record)
save_photos(crop)
else
Rails.logger.debug "\tcrop not found on Open Farm"
crop.update!(openfarm_data: false)
end
end
def save_companions(crop, openfarm_record)
companions = openfarm_record.fetch('data').fetch('relationships').fetch('companions').fetch('data')
crops = openfarm_record.fetch('included', []).select { |rec| rec["type"] == 'crops' }
CropCompanion.transaction do
companions.each do |com|
companion_crop_hash = crops.detect { |c| c.fetch('id') == com.fetch('id') }
companion_crop_name = companion_crop_hash.fetch('attributes').fetch('name').downcase
companion_crop = Crop.where('lower(name) = ?', companion_crop_name).first
companion_crop = Crop.create!(name: companion_crop_name, requester: @cropbot, approval_status: "pending") if companion_crop.nil?
crop.companions << companion_crop unless crop.companions.where(id: companion_crop.id).any?
end
end
end
def save_photos(crop)
pictures = fetch_pictures(crop.name)
pictures.each do |picture|
data = picture.fetch('attributes')
Rails.logger.debug(data)
next unless data.fetch('image_url').start_with? 'http'
next if Photo.find_by(source_id: picture.fetch('id'), source: 'openfarm')
photo = Photo.new(
source_id: picture.fetch('id'),
source: 'openfarm',
owner: @cropbot,
thumbnail_url: data.fetch('thumbnail_url'),
fullsize_url: data.fetch('image_url'),
title: 'Open Farm photo',
license_name: 'No rights reserved',
link_url: "https://openfarm.cc/en/crops/#{name_to_slug(crop.name)}"
)
if photo.valid?
Photo.transaction do
photo.save
PhotoAssociation.find_or_create_by! photo:, photographable: crop
end
Rails.logger.debug { "\t saved photo #{photo.id} #{photo.source_id}" }
else
Rails.logger.warn "Photo not valid"
end
end
end
def fetch(name)
conn.get("crops/#{name_to_slug(name)}.json").body
rescue NoMethodError
Rails.logger.debug "error fetching crop"
Rails.logger.debug "BODY: "
Rails.logger.debug body
end
def name_to_slug(name)
CGI.escape(name.gsub(' ', '-').downcase)
end
def fetch_all(page)
conn.get("crops.json?page=#{page}").body.fetch('data', {})
end
def fetch_pictures(name)
body = conn.get("crops/#{name_to_slug(name)}/pictures.json").body
body.fetch('data', false)
rescue StandardError
Rails.logger.debug "Error fetching photos"
Rails.logger.debug []
end
private
def conn
Faraday.new BASE do |conn|
conn.response :json, content_type: /\bjson$/
conn.adapter Faraday.default_adapter
end
end
end

View File

@@ -18,20 +18,10 @@ class TimelineService
.union_all(photos_query)
.union_all(seeds_query)
.union_all(activities_query)
.union_all(likes_query)
.where.not(event_at: nil)
.order(event_at: :desc)
end
def self.likes_query
Like
.select("likes.id",
"'like' as event_type",
"likes.created_at as event_at",
"likes.member_id as owner_id",
"null as crop_id")
end
def self.activities_query
Activity.select(
:id,

View File

@@ -3,7 +3,6 @@
%a#activity-actions-button.btn.btn-info.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", type: "button", href: '#'} Actions
.dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "planting-actions-button"}
= activity_edit_button(activity, classes: 'dropdown-item')
= activity_copy_button(activity, classes: 'dropdown-item')
- if activity.active
= activity_finish_button(activity, classes: 'dropdown-item')
.dropdown-divider

View File

@@ -10,8 +10,9 @@
%a.activity-menu.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", type: "button", href: '#'}
.dropdown-menu{"aria-labelledby" => "activity-menu"}
= activity_edit_button(activity, classes: 'dropdown-item')
= activity_copy_button(activity, classes: 'dropdown-item')
= link_to edit_activity_path(slug: activity.slug), class: 'dropdown-item' do
= edit_icon
= t('buttons.edit')
- if activity.active
= activity_finish_button(activity, classes: 'dropdown-item')
@@ -19,24 +20,16 @@
- if can? :destroy, activity
.dropdown-divider
= delete_button(activity, classes: 'dropdown-item text-danger')
.card-body
= link_to activity_path(slug: activity.slug) do
= link_to activity_path(slug: activity.slug) do
.card-body.text-center
%h4= activity.name
- if activity.due_date
%small.due-date{title: activity.due_date}
= standard_time_distance(activity.due_date.to_date, Time.zone.now.to_date)
%div
%small.text-justify{title: activity.description}= activity.description.truncate(150)
%p
%ul.list-unstyled
- if activity.garden_name && activity.garden_slug
%li
%small= link_to activity.garden_name, garden_path(slug: activity.garden_slug)
- if activity.planting_name && activity.planting_slug
%li
%small= link_to activity.planting_name, planting_path(slug: activity.planting_slug)
.text-center= activity.description
- if activity.garden
.text-center= activity.garden
- if activity.planting
.text-center= activity.planting
.card-footer
%small.chip.member-chip
= link_to member_path(slug: activity.owner_slug) do
= activity.owner_login_name
.float-right
%span.chip.member-chip
= link_to member_path(slug: activity.owner_slug) do
= activity.owner_login_name

View File

@@ -17,7 +17,7 @@
.row
.col-md-12
= f.text_field :name, required: true, label: 'What needs to be done?', autofocus: true
= f.text_field :name, required: true, label: 'What needs to be done?'
.row
.col-md-4
= f.select(:category, Activity::CATEGORIES, include_blank: true)
@@ -27,13 +27,13 @@
.row
.col-md-4
= f.collection_radio_buttons(:garden_id, @activity.owner.gardens.active.order_by_name,
= f.collection_radio_buttons(:garden_id, @activity.owner.gardens.active,
:id, :name,
label: 'Is this for a specific garden?')
= link_to "Add a garden.", new_garden_path
.col-md-4
= f.collection_radio_buttons(:planting_id, @activity.owner.plantings.active.recent,
= f.collection_radio_buttons(:planting_id, @activity.owner.plantings.active,
:id, :crop_name,
label: 'Is this for a specific planting?')
= link_to "Add a planting.", new_planting_path
@@ -43,14 +43,6 @@
value: @activity.due_date ? @activity.due_date.to_fs(:ymd) : '',
label: 'When?'
.row
.col-md-6
= label_tag :repeat_times, 'Repeat how many times?'
= number_field_tag :repeat_times, nil, class: 'form-control'
.col-md-6
= label_tag :repeat_weeks, 'Every how many weeks?'
= number_field_tag :repeat_weeks, nil, class: 'form-control'
%hr
.row

View File

@@ -12,7 +12,6 @@
%li.breadcrumb-item= link_to @activity.owner, member_activities_path(@activity.owner)
%li.breadcrumb-item.active= link_to @activity.name, @activity
.activity
.row
.col-md-8.col-xs-12
@@ -23,14 +22,9 @@
%small.text-muted= @activity.category
%tt
= @activity.due_date
- if @repeat_link
.alert.alert-info
Activity marked as finished recently. Would you like to repeat in the near future?
= link_to 'Repeat this activity', @repeat_link, class: 'btn btn-secondary btn-sm ml-2'
- if @activity.description.present?
:markdown
#{strip_tags markdownify(@activity.description)}
= render 'likes/likes', object: @activity
= render 'activities/actions', activity: @activity

View File

@@ -1,16 +0,0 @@
%h1= "Companions for #{@crop.name}"
= link_to 'New Companion', new_admin_crop_crop_companion_path(@crop), class: 'btn btn-primary'
%table.table
%thead
%tr
%th Name
%th Source URL
%th Actions
%tbody
- @crop_companions.each do |companion|
%tr
%td= companion.crop_b.name
%td= companion.source_url
%td= link_to 'Delete', admin_crop_crop_companion_path(@crop, companion), method: :delete, data: { confirm: 'Are you sure?' }

View File

@@ -1,6 +0,0 @@
%h1= "New Companion for #{@crop.name}"
= bootstrap_form_for [:admin, @crop, @crop_companion] do |f|
= f.collection_select :crop_b_id, Crop.order(:name), :id, :name, { label: 'Companion' }
= f.text_field :source_url, label: 'Source URL'
= f.submit 'Create'

View File

@@ -1,6 +1,6 @@
- if crop.approved? && signed_in?
.btn-group.crop-actions{"aria-label" => "Crop Actions", role: "group"}
.btn-group{"aria-label" => "Crop Actions", role: "group"}
= render 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
= render 'harvests/modal', harvest: Harvest.new(crop: @crop, owner: current_member)
= render 'seeds/modal', seed: Seed.new(crop: @crop, owner: current_member)

View File

@@ -25,9 +25,9 @@
Last harvest expected
%strong= crop.median_days_to_last_harvest
days after planting
- if member_signed_in?
.card-footer
.d-flex.btn-group-vertical
= render 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
- #= render 'harvests/modal', harvest: Harvest.new(crop: crop, owner: current_member)
= render 'seeds/modal', seed: Seed.new(crop: crop, owner: current_member)
- if member_signed_in?
.card-footer
.d-flex.justify-content-between
= render 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
- #= render 'harvests/modal', harvest: Harvest.new(crop: crop, owner: current_member)
= render 'seeds/modal', seed: Seed.new(crop: crop, owner: current_member)

View File

@@ -41,23 +41,11 @@
= f.radio_button(:perennial, true, label: "Perennial")
%span.help-block Living more than two years
%h2 OpenFarm Data
= f.text_area :description, label: 'Description'
= f.number_field :row_spacing, label: 'Row Spacing (cm)', min: 0
= f.number_field :spread, label: 'Spread (cm)', min: 0
= f.number_field :height, label: 'Height (cm)', min: 0
= f.text_field :sowing_method
= f.text_field :sun_requirements
= f.number_field :growing_degree_days, min: 0
- unless @crop.approved?
= link_to 'Search wikipedia', "https://en.wikipedia.org/w/index.php?search=#{@crop.name}", target: '_blank'
= f.url_field :en_wikipedia_url, id: "en_wikipedia_url", label: 'Wikipedia URL'
%span.help-block
Link to the crop's page on the English language Wikipedia (required).
= f.url_field :en_youtube_url, label: 'YouTube URL'
%span.help-block
Link to a YouTube video about the crop in English.
-# Only crop wranglers see the crop hierarchy (for now)
- if can? :wrangle, @crop

View File

@@ -12,7 +12,7 @@
#{harvest.owner} harvested #{display_quantity(harvest)}.
.float-right= render 'members/location', member: harvest.owner
.harvest-timeago
%small #{standard_time_distance(harvest.harvested_at, Time.zone.now.to_date)}
%small #{distance_of_time_in_words(harvest.harvested_at, Time.zone.now)} ago.
%li.list-group-item= link_to "View all #{crop.name} harvests", crop_harvests_path(crop), class: 'card-link'
- if crop.approved?
- if current_member

View File

@@ -1,33 +0,0 @@
- if crop.row_spacing || crop.spread || crop.height || crop.sowing_method || crop.sun_requirements || crop.growing_degree_days
= cute_icon
.card
.card-body
%h4 OpenFarm Data
%ul.list-group.list-group-flush
- if crop.row_spacing
%li.list-group-item
%strong Row Spacing:
= crop.row_spacing
cm
- if crop.spread
%li.list-group-item
%strong Spread:
= crop.spread
cm
- if crop.height
%li.list-group-item
%strong Height:
= crop.height
cm
- if crop.sowing_method
%li.list-group-item
%strong Sowing Method:
= crop.sowing_method
- if crop.sun_requirements
%li.list-group-item
%strong Sun Requirements:
= crop.sun_requirements
- if crop.growing_degree_days
%li.list-group-item
%strong Growing Degree Days:
= crop.growing_degree_days

View File

@@ -0,0 +1,6 @@
- if crop.guides_count.present? && crop.guides_count.positive?
%p
There are
= link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name.gsub(' ', '-').downcase}" do
#{crop.guides_count} growing guides on Open Farm

View File

@@ -14,10 +14,6 @@
= icon 'far', 'update'
Fetch data from GBIF
= link_to admin_crop_crop_companions_path(crop), class: 'dropdown-item' do
= icon 'fas', 'leaf'
Manage Companions
- if can? :destroy, crop
.dropdown-divider
= delete_button(crop, classes: 'dropdown-item text-danger')

View File

@@ -24,18 +24,12 @@
%section.prediction
= cute_icon
= render 'predictions', crop: @crop
- if @crop.all_companions.any?
- if @crop.companions.any?
%section.companions
%h2 Companions
- @crop.all_companions.each do |companion|
- @crop.companions.each do |companion|
= render 'crops/tiny', crop: companion
- if @crop.en_youtube_url.present?
%section.youtube
%h2 Video
.embed-responsive.embed-responsive-16by9
%iframe.embed-responsive-item{ src: "https://www.youtube.com/embed/#{youtube_video_id(@crop.en_youtube_url)}", allowfullscreen: true }
%section.photos
= cute_icon
= render 'crops/photos', crop: @crop
@@ -80,6 +74,7 @@
.card-body
%h4 How to grow #{@crop.name.pluralize}
= render 'grown_for', crop: @crop
= render 'planting_advice', crop: @crop
- if @crop.parent
%hr/
%p.parent-crop
@@ -117,8 +112,6 @@
= render 'harvests', crop: @crop
= render 'find_seeds', crop: @crop
= render 'openfarm_data', crop: @crop
= cute_icon
.card
.card-body
@@ -131,6 +124,13 @@
= icon 'fas', 'external-link-alt'
Wikipedia (English)
%li.list-group-item
= link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name.gsub(' ', '-')}",
class: 'card-link',
target: "_blank",
rel: "noopener noreferrer" do
= icon 'fas', 'external-link-alt'
OpenFarm - Growing guide
%li.list-group-item
= link_to "https://www.gardenate.com/plant/#{CGI.escape @crop.name}",
target: "_blank",
@@ -163,10 +163,3 @@
= icon 'fas', 'external-link-alt'
Wikihow instructions
%li.list-group-item
= link_to "https://www.youtube.com/results?search_query=#{CGI.escape "growing #{@crop.name}"}",
target: "_blank",
class: 'card-link',
rel: "noopener noreferrer" do
= icon 'fab', 'youtube'
YouTube

View File

@@ -15,16 +15,3 @@
method: :delete, class: "remove btn btn-danger"
- else
= link_to 'Connect to Flickr', '/members/auth/flickr', class: 'btn'
%hr
.row
.col-md-12
%p
= image_tag "icons/post.svg", size: "32x32", alt: 'API logo'
- if current_member.api_token?
Your API token is
%code= current_member.api_token.token
= link_to "Regenerate", regenerate_api_token_path,
data: { confirm: "Are you sure? Your old token will stop working immediately." },
method: :post, class: "remove btn btn-danger"
- else
= link_to 'Generate API Token', regenerate_api_token_path, method: :post, class: 'btn btn-primary'

View File

@@ -15,4 +15,5 @@
- if can?(:destroy, garden)
.dropdown-divider
= delete_button(garden, classes: 'dropdown-item text-danger', message: 'gardens.confirm_delete')
= delete_button(garden, classes: 'dropdown-item text-danger',
message: 'All plantings associated with this garden will also be deleted. Are you sure?')

View File

@@ -16,8 +16,3 @@
.col-md-12
%p Nothing has been planted here.
- if @finished_activities&.size&.positive?
%h2 Finished activities in garden
.index-cards
- @finished_activities.each do |activity|
= render "activities/card", activity: activity

View File

@@ -11,9 +11,6 @@
.row
.col-md-2
%small
%a{href: "#content"}
Skip to main content
= render 'layouts/nav', model: Garden
%label
= link_to show_inactive_tickbox_path('gardens', owner: @owner, show_all: @show_all) do
@@ -23,7 +20,7 @@
%hr/
= render @owner
.col-md-10#content
.col-md-10
- if @gardens.empty?
%p There are no gardens to display.
- if can?(:create, Garden) && @owner == current_member

View File

@@ -66,7 +66,8 @@
- if can?(:destroy, @garden)
.dropdown-divider
= delete_button(@garden, classes: 'dropdown-item text-danger', message: 'gardens.confirm_delete')
= delete_button(@garden, classes: 'dropdown-item text-danger',
message: 'All plantings associated with this garden will also be deleted. Are you sure?')
%section
%h2 Current activities in garden

View File

@@ -5,8 +5,3 @@
- @matching_plantings.each do |planting|
= f.radio_button :planting_id, planting.id, label: planting
= f.submit "save", class: 'btn btn-sm'
- if @harvest.planting.present? && @harvest.planting.overall_rating.blank?
.alert.alert-info{role: "alert"}
This harvest is from a planting that hasn't been rated yet.
= link_to "Rate this planting", edit_planting_path(@harvest.planting, anchor: "planting_overall_rating"), class: 'alert-link'

View File

@@ -46,7 +46,7 @@
%h3
Harvested
= editable :date, @harvest, :harvested_at, display_field: '.harvested_at'
%strong.harvested_at #{standard_time_distance @harvest.harvested_at, Time.zone.now.to_date}
%strong.harvested_at #{distance_of_time_in_words @harvest.harvested_at, Time.zone.now.to_date} ago
%span.harvested_at= I18n.l @harvest.harvested_at
.card{class: @harvest.quantity.present? ? '' : 'text-muted'}

View File

@@ -1,11 +0,0 @@
%section.releases
= cute_icon
- github_releases.each do |release|
%h2= link_to release[:title], release[:link], target: "_blank"
.card
.card-header
%small= release[:updated].to_date.to_s
.card-body= release[:content].html_safe
%p
= link_to 'Find out more »', 'https://github.com/Growstuff/growstuff/releases', target: "_blank", class: 'btn btn-block'

View File

@@ -4,6 +4,6 @@
member: link_to(t('.member_linktext', count: Member.confirmed.size.to_i), members_path),
number_crops: link_to(t('.number_crops_linktext', count: Crop.count.to_i), crops_path),
number_plantings: link_to(t('.number_plantings_linktext', count: Planting.count.to_i), plantings_path),
number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path),
contributors: link_to(count_github_contibutors, 'https://github.com/Growstuff/growstuff/blob/dev/CONTRIBUTORS.md', target: '_blank', rel: 'noopener'),
github: link_to('GitHub', 'http://github.com/Growstuff/growstuff', target: '_blank', rel: 'noopener'))
number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path))

View File

@@ -8,14 +8,13 @@
%p= render 'stats', cached: true
.col
%br
%p
- if current_member.plantings.active.any?
= link_to member_path(current_member, anchor: "content"), class: 'btn btn-dark' do
= link_to member_path(current_member, anchor: "#content"), class: 'btn btn-dark' do
= planting_icon
Track my plantings
%p
= link_to member_gardens_path(current_member, anchor: "content"), class: 'btn btn-dark' do
= link_to member_gardens_path(current_member), class: 'btn btn-dark' do
= garden_icon
Show me my garden
- else
@@ -62,19 +61,3 @@
%section.members
= cute_icon
= render 'members', cached: true
.row
.col-12.col-lg-6
%section.pwa-install
= cute_icon
%h2.text-center= t('home.pwa_title')
.index-cards
.card
.card-body
%h3= t('home.pwa_ios_title')
%p= t('home.pwa_ios_steps_html')
.card
.card-body
%h3= t('home.pwa_android_title')
%p= t('home.pwa_android_steps_html')
.col-12.col-lg-6
= render 'releases'

View File

@@ -4,8 +4,8 @@
= image_tag("pear.png", class: 'logo', alt: 'Growstuff brand logo (drawing of a pear)')
.d-none.d-md-block
%span.site-name Growstuff
%button.navbar-toggler{ "aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "offcanvas", type: "button" }
%i.fas.fa-ellipsis-v.navbar-toggler-icon
.nav= render 'crops/search_bar'
.nav
%button.navbar-toggler.ml-auto{ "aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", type: "button" }
%span.navbar-toggler-icon
= render 'layouts/menu'

View File

@@ -1,11 +1,11 @@
#navbarSupportedContent.collapse.navbar-collapse
%ul.navbar-nav.mr-auto.bg-dark
#navbarSupportedContent.offcanvas.offcanvas-top
%ul.navbar-nav.mr-auto
- if signed_in?
%li.nav-item
= link_to timeline_index_path, method: :get, class: 'nav-link text-white', title: "Timeline" do
= link_to timeline_index_path, method: :get, class: 'nav-link text-white' do
= image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications"
%li.nav-item
= link_to member_gardens_path(current_member, anchor: "content"), class: 'nav-link text-white', title: "My gardens" do
= link_to member_gardens_path(current_member), class: 'nav-link text-white', title: "My gardens" do
= image_icon 'gardens'
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
@@ -30,9 +30,7 @@
- cache("everyone-menu", expires_in: 1.week) do
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
%span
= t('.crops')
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.crops')
.dropdown-menu
= link_to crops_path, class: 'dropdown-item' do
= t('.browse_crops')
@@ -46,9 +44,7 @@
= harvest_icon
= t('.harvests')
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
%span
= t('.community')
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.community')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
= link_to t('.community_map'), places_path, class: 'dropdown-item'
= link_to t('.browse_members'), members_path, class: 'dropdown-item'
@@ -58,9 +54,7 @@
- if member_signed_in?
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
%span
= t('.admin')
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.admin')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
- if current_member.role?(:crop_wrangler)
= link_to t('.crop_wrangling'), wrangle_crops_path, class: 'dropdown-item'

View File

@@ -1 +0,0 @@
#{link_to event_model.member, event_model.member} liked #{link_to event_model.likeable.class.name.downcase, event_model.likeable}

View File

@@ -1,5 +1,5 @@
- cache member do
.card
.card.card-double
.card-body
%h4.login-name= link_to member, member
%div

View File

@@ -28,7 +28,7 @@
%a{href: "#content"}
Skip to main content
- if @member.bio.blank?
- if member_signed_in? && current_member == @member
- if can? :edit, @member
= link_to "Add a bio to complete your profile.", edit_member_registration_path
- else
#{@member.login_name} hasn't written a bio yet.
@@ -82,15 +82,15 @@
.col-md-10#content
.row
%section.order-3.order-md-1.col-12= render "map", member: @member
- if @harvesting.size.positive?
%section.harvests.order-2.order-md-1.col-12#harvests
- if @harvesting.size.positive?
%section.harvests.order-2.order-md-1
%h2 Ready to harvest
.index-cards
- @harvesting.each do |planting|
= render 'plantings/thumbnail', planting: planting
- if @others.size.positive?
%section.planting-progress.order-2.order-md-1.col-12#planting-progress
%section.planting-progress.order-2.order-md-1.col-12
%h2 Progress report
%p Still growing and not ready for harvesting.
.list-group
@@ -99,7 +99,7 @@
%span= render 'plantings/tiny', planting: planting
%span= render 'plantings/progress', planting: planting
- if @late.size.positive?
%section.late.order-2.order-md-1.col-12#late
%section.late.order-2.order-md-1.col-12
%h2 Late
%p
These plantings are at the end of their lifecycle.
@@ -109,7 +109,7 @@
- @late.each do |planting|
= render 'plantings/thumbnail', planting: planting
- if @super_late.any?
%section.superlate.order-2.order-md-1.col-12#superlate
%section.superlate.order-2.order-md-1.col-12
%h2 Super late
%p
We suspect the following plantings finished long ago and no longer need tracking.
@@ -122,14 +122,14 @@
planted on #{planting.planted_at.to_date}
- if @harvests.any?
%section.havests.order-2.order-md-1.col-12#recent-harvests
%section.havests.order-2.order-md-1.col-12
%h2 Recent Harvests
.index-cards
- @harvests.each do |harvest|
= render 'harvests/thumbnail', harvest: harvest
- if @activity.any?
%section.activity.order-2.order-md-1.col-12#activity
%section.activity.order-2.order-md-1.col-12
%h2 Activity
.list-group
- @activity.each do |event|

View File

@@ -21,7 +21,7 @@
- if planting.finish_is_predicatable?
.card.fact-card
%h3 Progress
- if planting.age_in_days.to_i < 0
- if planting.age_in_days < 0
%strong Planned
- else
%strong #{planting.age_in_days}/#{planting.expected_lifespan}
@@ -45,7 +45,7 @@
%h3 Growing
%strong= seedling_icon
%span
- if planting.age_in_days.to_i < 0
- if planting.age_in_days < 0
Planting planned
- else
Planting is still growing today
@@ -89,9 +89,3 @@
- if planting.finished_at.present?
%span.plantingfact--finish
= planting.finished_at.year
- if planting.overall_rating.present?
.card.fact-card
.card-body
%h3 Overall Rating
%p.card-text
%strong= "#{planting.overall_rating}/5"

View File

@@ -27,7 +27,7 @@
.row
.col-md-8
= f.collection_radio_buttons(:garden_id, @planting.owner.gardens.active.order_by_name,
= f.collection_radio_buttons(:garden_id, @planting.owner.gardens.active,
:id, :name, required: true,
label: 'Where did you plant it?')
= link_to "Add a garden.", new_garden_path
@@ -43,15 +43,6 @@
= f.select(:sunniness, Planting::SUNNINESS_VALUES, { include_blank: '', label: 'Sun or shade?' } )
.col-md-4
= f.number_field :quantity, label: 'How many?', min: 1
.col-md-12
= f.range_field :overall_rating, min: 1, max: 5, include_blank: 'Leave blank', label: 'Overall Rating', list: "rating-list", title: "How well is the planting going?"
%datalist{"id": "rating-list"}
%option{"value": "1"} Poor
%option{"value": "2"}
%option{"value": "3"}
%option{"value": "4"}
%option{"value": "5"} Great
= f.text_area :description, rows: 6, label: 'Tell us more about it'
.row

View File

@@ -9,7 +9,7 @@
%p Which garden is the planting in?
%ul.list-group
- planting.owner.gardens.active.order_by_name.each do |garden|
- planting.owner.gardens.active.order(:name).each do |garden|
%li.list-group-item
= link_to plantings_path(planting: {crop_id: planting.crop_id, garden_id: garden.id}), method: :post do
.md-v-line

View File

@@ -7,15 +7,6 @@
= tag("meta", property: "og:type", content: "website")
= tag("meta", property: "og:url", content: request.original_url)
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
- if @planting.overall_rating.present?
%script{type: "application/ld+json"}
:plain
{
"@context": "http://schema.org",
"@type": "Rating",
"ratingValue": "#{@planting.overall_rating}",
"bestRating": "5"
}
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Plantings', plantings_path
@@ -48,10 +39,10 @@
- elsif @planting.percentage_grown.present?
#{@planting.percentage_grown.to_i}%
- if @planting.finish_is_predicatable?
- if @planting.age_in_days.to_i < 0
- if @planting.age_in_days < 0
%strong Planned
- else
%strong #{@planting.age_in_days.to_i}/#{@planting.expected_lifespan} days
%strong #{@planting.age_in_days}/#{@planting.expected_lifespan} days
= render 'timeline', planting: @planting
= render 'likes/likes', object: @planting
@@ -82,10 +73,6 @@
%section
%h2 Current activities for planting
- if @cultivate_soil_link
.alert.alert-info
This was the last planting recently in this bed. Would you like to cultivate the soil soon?
= link_to 'Cultivate soil', @cultivate_soil_link, class: 'btn btn-secondary btn-sm ml-2'
.index-cards
- if @current_activities&.size&.positive?
- @current_activities.each do |activity|
@@ -93,11 +80,7 @@
- else
.col-md-12
%p Nothing is currently planned here.
- if @finished_activities&.size&.positive?
%h2 Finished activities for planting
.index-cards
- @finished_activities.each do |activity|
= render "activities/card", activity: activity
.col-md-4.col-xs-12
= render @planting.crop

View File

@@ -16,20 +16,14 @@
%p
- if seed.quantity
.badge.badge-info #{seed.quantity} seeds
%ul
- if seed.organic != 'unknown'
%li
%small.seedtitle--organic= seed.organic
- if seed.gmo != 'unknown'
%li
%small.seedtitle--gmo= seed.gmo
- if seed.heirloom != 'unknown'
%li
%small.seedtitle--heirloom= seed.heirloom
.card-footer
.d-flex.w-100.justify-content-between
- if seed.tradable
- if seed.organic != 'unknown'
.badge.badge-success.seedtitle--organic= seed.organic
- if seed.gmo != 'unknown'
.badge.badge-success.seedtitle--gmo= seed.gmo
- if seed.heirloom != 'unknown'
.badge.badge-success.seedtitle--heirloom= seed.heirloom
- if seed.tradable
.card-footer
.d-flex.w-100.justify-content-between
%small Will trade #{seed.tradable_to}
- if seed.plant_before
%small Plant before #{seed.plant_before}
/ %a.btn.btn-sm{href: "#"} Request

Some files were not shown because too many files have changed in this diff Show More