Compare commits

..

1 Commits

Author SHA1 Message Date
Daniel O'Connor
fea2f6ff61 Fix deprecations 2025-08-27 14:28:14 +00:00
121 changed files with 410 additions and 1561 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -89,7 +89,7 @@ jobs:
sudo apt-get -y install libpq-dev google-chrome-stable sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS - name: Install NodeJS
uses: actions/setup-node@v5 uses: actions/setup-node@v4
with: with:
node-version: '12' node-version: '12'

View File

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

View File

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

View File

@@ -178,6 +178,7 @@ group :development, :test do
gem 'dotenv-rails' gem 'dotenv-rails'
# cli utils # cli utils
gem 'haml-i18n-extractor', require: false
gem 'haml_lint', '>= 0.25.1', require: false # Checks haml files for goodness 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 'i18n-tasks', require: false # adds tests for finding missing and unused translations
gem 'rspectre', require: false # finds unused code in specs gem 'rspectre', require: false # finds unused code in specs

View File

@@ -142,7 +142,7 @@ GEM
erubi (>= 1.0.0) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
rouge (>= 1.0.0) rouge (>= 1.0.0)
bigdecimal (3.2.3) bigdecimal (3.2.2)
bluecloth (2.2.0) bluecloth (2.2.0)
bonsai-elasticsearch-rails (7.0.1) bonsai-elasticsearch-rails (7.0.1)
elasticsearch-model (< 8) elasticsearch-model (< 8)
@@ -183,7 +183,7 @@ GEM
image_processing (~> 1.1) image_processing (~> 1.1)
marcel (~> 1.0.0) marcel (~> 1.0.0)
ssrf_filter (~> 1.0) ssrf_filter (~> 1.0)
chartkick (5.2.0) chartkick (5.1.5)
childprocess (5.0.0) childprocess (5.0.0)
coderay (1.1.3) coderay (1.1.3)
coercible (1.0.0) coercible (1.0.0)
@@ -198,7 +198,7 @@ GEM
comfy_bootstrap_form (4.0.9) comfy_bootstrap_form (4.0.9)
rails (>= 5.0.0) rails (>= 5.0.0)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.5)
connection_pool (2.5.4) connection_pool (2.5.3)
crass (1.0.6) crass (1.0.6)
crowdin-api (1.12.0) crowdin-api (1.12.0)
open-uri (>= 0.1.0, < 0.2.0) open-uri (>= 0.1.0, < 0.2.0)
@@ -257,9 +257,9 @@ GEM
excon (1.2.5) excon (1.2.5)
logger logger
execjs (2.10.0) execjs (2.10.0)
factory_bot (6.5.5) factory_bot (6.5.4)
activesupport (>= 6.1.0) activesupport (>= 6.1.0)
factory_bot_rails (6.5.1) factory_bot_rails (6.5.0)
factory_bot (~> 6.5) factory_bot (~> 6.5)
railties (>= 6.1.0) railties (>= 6.1.0)
faker (3.5.2) faker (3.5.2)
@@ -294,6 +294,12 @@ GEM
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
tilt tilt
haml-i18n-extractor (0.5.9)
activesupport
haml
highline
tilt
trollop (= 1.16.2)
haml-rails (2.1.0) haml-rails (2.1.0)
actionpack (>= 5.1) actionpack (>= 5.1)
activesupport (>= 5.1) activesupport (>= 5.1)
@@ -457,8 +463,8 @@ GEM
racc racc
percy-capybara (5.0.0) percy-capybara (5.0.0)
capybara (>= 3) capybara (>= 3)
pg (1.6.2) pg (1.6.1)
pg (1.6.2-x86_64-linux) pg (1.6.1-x86_64-linux)
platform-api (3.8.0) platform-api (3.8.0)
heroics (~> 0.1.1) heroics (~> 0.1.1)
moneta (~> 1.0.0) moneta (~> 1.0.0)
@@ -475,7 +481,7 @@ GEM
date date
stringio stringio
public_suffix (6.0.1) public_suffix (6.0.1)
puma (7.0.2) puma (6.6.1)
nio4r (~> 2.0) nio4r (~> 2.0)
query_diet (0.7.2) query_diet (0.7.2)
racc (1.8.1) racc (1.8.1)
@@ -601,7 +607,7 @@ GEM
rswag-ui (2.16.0) rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1) actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1) railties (>= 5.2, < 8.1)
rubocop (1.80.2) rubocop (1.80.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0) lint_roller (~> 1.1.0)
@@ -630,7 +636,7 @@ GEM
rubocop-rake (0.7.1) rubocop-rake (0.7.1)
lint_roller (~> 1.1) lint_roller (~> 1.1)
rubocop (>= 1.72.1) rubocop (>= 1.72.1)
rubocop-rspec (3.7.0) rubocop-rspec (3.6.0)
lint_roller (~> 1.1) lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1) rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0) rubocop-rspec_rails (2.31.0)
@@ -688,13 +694,14 @@ GEM
temple (0.10.4) temple (0.10.4)
terminal-table (4.0.0) terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4) unicode-display_width (>= 1.1.1, < 4)
terser (1.2.6) terser (1.2.5)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
thor (1.4.0) thor (1.4.0)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.6.1) tilt (2.6.1)
timecop (0.9.10) timecop (0.9.10)
timeout (0.4.3) timeout (0.4.3)
trollop (1.16.2)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (3.1.5) unicode-display_width (3.1.5)
@@ -781,6 +788,7 @@ DEPENDENCIES
gibbon (~> 1.2.0) gibbon (~> 1.2.0)
gravatar-ultimate gravatar-ultimate
haml haml
haml-i18n-extractor
haml-rails haml-rails
haml_lint (>= 0.25.1) haml_lint (>= 0.25.1)
hashie (>= 3.5.3) hashie (>= 3.5.3)

View File

@@ -17,9 +17,7 @@ encourage participation from people of all backgrounds and skill levels.
## Want to contribute? ## 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. 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! 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.
## Important links ## 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). * 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). * 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 ## For designers, writers, researchers, data wranglers, and other contributors
There are heaps of ways to get involved and contribute no matter what There are heaps of ways to get involved and contribute no matter what

View File

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

View File

@@ -1,8 +1,6 @@
// stats shown on homepage. eg. "999 members..." // stats shown on homepage. eg. "999 members..."
.stats { .stats {
a { font-weight: bold;
font-weight: bold;
}
} }
.crops, .crops,

View File

@@ -10,33 +10,9 @@
width: 100%; width: 100%;
} }
#navbarSupportedContent { .navbar .nav > li {
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;
display: block; display: block;
} }
}
.navbar .navbar-form { .navbar .navbar-form {
padding-left: 0; padding-left: 0;

View File

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

View File

@@ -29,7 +29,7 @@ class ActivitiesController < DataController
def new def new
@activity = Activity.new( @activity = Activity.new(
owner: current_member, owner: current_member,
due_date: Date.today due_date: Date.today
) )
if params[:garden_id] if params[:garden_id]

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 module V1
class BaseController < JSONAPI::ResourceController class BaseController < JSONAPI::ResourceController
abstract 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 end
end end

View File

@@ -39,6 +39,12 @@ class CropsController < ApplicationController
respond_with @crops respond_with @crops
end end
def openfarm
@crop = Crop.find(params[:crop_slug])
@crop.update_openfarm_data!
respond_with @crop, location: @crop
end
def gbif def gbif
@crop = Crop.find(params[:crop_slug]) @crop = Crop.find(params[:crop_slug])
@crop.update_gbif_data! @crop.update_gbif_data!
@@ -131,6 +137,7 @@ class CropsController < ApplicationController
if @crop.approval_status_changed?(from: "pending", to: "approved") if @crop.approval_status_changed?(from: "pending", to: "approved")
notifier.deliver_now! notifier.deliver_now!
@crop.update_openfarm_data!
@crop.update_gbif_data! @crop.update_gbif_data!
end end
else else
@@ -192,8 +199,6 @@ class CropsController < ApplicationController
:parent_id, :perennial, :parent_id, :perennial,
:request_notes, :reason_for_rejection, :request_notes, :reason_for_rejection,
:rejection_notes, :rejection_notes,
:row_spacing, :spread, :height,
:sowing_method, :sun_requirements, :growing_degree_days,
scientific_names_attributes: %i(scientific_name _destroy id) scientific_names_attributes: %i(scientific_name _destroy id)
) )
end end

View File

@@ -4,7 +4,7 @@ class GardensController < DataController
def index def index
@owner = Member.find_by(slug: params[:member_slug]) @owner = Member.find_by(slug: params[:member_slug])
@show_all = params[:all] == '1' @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.includes(:owner)
@gardens = @gardens.active unless @show_all @gardens = @gardens.active unless @show_all
@@ -18,9 +18,8 @@ class GardensController < DataController
end end
def show 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) @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) @finished_plantings = @garden.plantings.finished.includes(:crop)
@suggested_companions = Crop.approved.where( @suggested_companions = Crop.approved.where(
id: CropCompanion.where(crop_a_id: @current_plantings.select(:crop_id)).select(:crop_b_id) id: CropCompanion.where(crop_a_id: @current_plantings.select(:crop_id)).select(:crop_b_id)

View File

@@ -37,7 +37,6 @@ class PlantingsController < DataController
@photos = @planting.photos.includes(:owner).order(date_taken: :desc) @photos = @planting.photos.includes(:owner).order(date_taken: :desc)
@harvests = Harvest.search(where: { planting_id: @planting.id }) @harvests = Harvest.search(where: { planting_id: @planting.id })
@current_activities = @planting.activities.current.includes(:owner).order(created_at: :desc) @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 @matching_seeds = matching_seeds
@crop = @planting.crop @crop = @planting.crop
@@ -134,7 +133,7 @@ class PlantingsController < DataController
:crop_id, :description, :garden_id, :planted_at, :crop_id, :description, :garden_id, :planted_at,
:parent_seed_id, :parent_seed_id,
:quantity, :sunniness, :planted_from, :finished, :quantity, :sunniness, :planted_from, :finished,
:finished_at, :failed, :overall_rating :finished_at, :failed
) )
end 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. prepend_before_action :check_captcha, only: [:create] # Change this to be any actions you want to protect with recaptcha.
def edit def edit
@flickr_auth = current_member.auth('flickr') @flickr_auth = current_member.auth('flickr')
render "edit" render "edit"
end end
@@ -38,12 +38,6 @@ class RegistrationsController < Devise::RegistrationsController
end end
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 def destroy
if @member.valid_password?(params.require(:member)[:current_password]) if @member.valid_password?(params.require(:member)[:current_password])
@member.discard @member.discard

View File

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

View File

@@ -21,32 +21,6 @@ module ApplicationHelper
classes classes
end 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. # Produces a cache key for uniquely identifying cached fragments.
def cache_key_for(klass, identifier = "all") def cache_key_for(klass, identifier = "all")
count = klass.count count = klass.count

View File

@@ -53,7 +53,7 @@ module ButtonsHelper
link_to t('buttons.mark_as_inactive'), link_to t('buttons.mark_as_inactive'),
garden_path(garden, garden: { active: 0 }), garden_path(garden, garden: { active: 0 }),
method: :put, class: classes, 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 end
def create_button(model_to_create, path, icon, label) def create_button(model_to_create, path, icon, label)

View File

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

View File

@@ -30,20 +30,4 @@ class Activity < ApplicationRecord
def to_s def to_s
name name
end 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 end

View File

@@ -4,10 +4,22 @@ module OpenFarmData
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
def update_openfarm_data!
OpenfarmService.new.update_crop(self)
end
def of_photo def of_photo
fetch_attr('main_image_path') fetch_attr('main_image_path')
end end
def height
fetch_attr('height')
end
def spread
fetch_attr('spread')
end
def svg_icon def svg_icon
icon = fetch_attr('svg_icon') icon = fetch_attr('svg_icon')
return icon if icon.present? return icon if icon.present?
@@ -23,18 +35,38 @@ module OpenFarmData
fetch_attr('description') fetch_attr('description')
end end
def row_spacing
fetch_attr('row_spacing')
end
def common_names def common_names
fetch_attr('common_names') fetch_attr('common_names')
end end
def guides_count
fetch_attr('guides_count')
end
def binomial_name def binomial_name
fetch_attr('binomial_name') fetch_attr('binomial_name')
end end
def sowing_method
fetch_attr('sowing_method')
end
def main_image_path def main_image_path
fetch_attr('main_image_path') fetch_attr('main_image_path')
end end
def sun_requirements
fetch_attr('sun_requirements')
end
def growing_degree_days
fetch_attr('growing_degree_days')
end
def processing_pictures def processing_pictures
fetch_attr('processing_pictures') fetch_attr('processing_pictures')
end end

View File

@@ -94,9 +94,9 @@ module PredictPlanting
private private
def calculate_percentage_grown 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) (percent > 100 ? 100 : percent)
end end
end end

View File

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

View File

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

View File

@@ -24,20 +24,6 @@ class Member < ApplicationRecord
has_many :notifications, foreign_key: 'recipient_id', inverse_of: :recipient 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 :sent_notifications, foreign_key: 'sender_id', inverse_of: :sender, class_name: "Notification"
has_many :authentications, dependent: :destroy 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 :photos, inverse_of: :owner
has_many :likes, dependent: :destroy has_many :likes, dependent: :destroy

View File

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

View File

@@ -83,9 +83,6 @@ class Planting < ApplicationRecord
validates :planted_from, allow_blank: true, inclusion: { validates :planted_from, allow_blank: true, inclusion: {
in: PLANTED_FROM_VALUES, message: "%<value>s is not a valid planting method" 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 def planting_slug
[ [

View File

@@ -12,8 +12,6 @@ class Seed < ApplicationRecord
ORGANIC_VALUES = ['certified organic', 'non-certified organic', 'conventional/non-organic', 'unknown'].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 GMO_VALUES = ['certified GMO-free', 'non-certified GMO-free', 'GMO', 'unknown'].freeze
HEIRLOOM_VALUES = %w(heirloom hybrid 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 # Relationships
@@ -46,9 +44,6 @@ class Seed < ApplicationRecord
validates :heirloom, allow_blank: false, validates :heirloom, allow_blank: false,
inclusion: { in: HEIRLOOM_VALUES, message: "You must say whether the seeds" \ inclusion: { in: HEIRLOOM_VALUES, message: "You must say whether the seeds" \
"are heirloom, hybrid, or unknown" } "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 # Delegations
@@ -64,7 +59,6 @@ class Seed < ApplicationRecord
scope :has_location, -> { joins(:owner).where.not('members.location': nil) } scope :has_location, -> { joins(:owner).where.not('members.location': nil) }
scope :recent, -> { order(created_at: :desc) } scope :recent, -> { order(created_at: :desc) }
scope :active, -> { where('finished <> true').where('finished_at IS NULL OR finished_at < ?', Time.zone.now) } scope :active, -> { where('finished <> true').where('finished_at IS NULL OR finished_at < ?', Time.zone.now) }
scope :expired, -> { active.where('plant_before < ?', Time.zone.today) }
def tradable def tradable
tradable_to != 'nowhere' tradable_to != 'nowhere'

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'
has_one :garden
has_one :planting
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 Api
module V1 module V1
class CropResource < BaseResource class CropResource < BaseResource
immutable # TODO: Re-evaluate this later immutable
filter :approval_status, default: 'approved' filter :approval_status, default: 'approved'
has_many :plantings has_many :plantings

View File

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

View File

@@ -3,17 +3,11 @@
module Api module Api
module V1 module V1
class HarvestResource < BaseResource class HarvestResource < BaseResource
before_save do immutable
@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
has_one :crop has_one :crop
has_one :planting has_one :planting
has_one :owner, class_name: 'Member' has_one :owner, class_name: 'Member'
# has_one :plant_part
has_many :photos has_many :photos
attribute :harvested_at attribute :harvested_at
@@ -22,15 +16,6 @@ module Api
attribute :weight_quantity attribute :weight_quantity
attribute :weight_unit attribute :weight_unit
attribute :si_weight 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 end
end end

View File

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

View File

@@ -3,9 +3,7 @@
module Api module Api
module V1 module V1
class PlantingResource < BaseResource class PlantingResource < BaseResource
before_create do immutable
@model.owner = context[:current_user]
end
has_one :garden has_one :garden
has_one :crop has_one :crop
@@ -38,10 +36,6 @@ module Api
filter :owner filter :owner
filter :owner_id filter :owner_id
filter :finished 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 attribute :percentage_grown
delegate :percentage_grown, to: :@model delegate :percentage_grown, to: :@model

View File

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

View File

@@ -1,16 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class BaseResource < JSONAPI::Resource class BaseResource < JSONAPI::Resource
immutable
abstract abstract
[: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 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(photos_query)
.union_all(seeds_query) .union_all(seeds_query)
.union_all(activities_query) .union_all(activities_query)
.union_all(likes_query)
.where.not(event_at: nil) .where.not(event_at: nil)
.order(event_at: :desc) .order(event_at: :desc)
end 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 def self.activities_query
Activity.select( Activity.select(
:id, :id,

View File

@@ -20,24 +20,16 @@
- if can? :destroy, activity - if can? :destroy, activity
.dropdown-divider .dropdown-divider
= delete_button(activity, classes: 'dropdown-item text-danger') = 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 %h4= activity.name
- if activity.due_date .text-center= activity.description
%small.due-date{title: activity.due_date} - if activity.garden
= standard_time_distance(activity.due_date.to_date, Time.zone.now.to_date) .text-center= activity.garden
%div - if activity.planting
%small.text-justify{title: activity.description}= activity.description.truncate(150) .text-center= activity.planting
%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)
.card-footer .card-footer
%small.chip.member-chip .float-right
= link_to member_path(slug: activity.owner_slug) do %span.chip.member-chip
= activity.owner_login_name = link_to member_path(slug: activity.owner_slug) do
= activity.owner_login_name

View File

@@ -27,13 +27,13 @@
.row .row
.col-md-4 .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, :id, :name,
label: 'Is this for a specific garden?') label: 'Is this for a specific garden?')
= link_to "Add a garden.", new_garden_path = link_to "Add a garden.", new_garden_path
.col-md-4 .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, :id, :crop_name,
label: 'Is this for a specific planting?') label: 'Is this for a specific planting?')
= link_to "Add a planting.", new_planting_path = link_to "Add a planting.", new_planting_path

View File

@@ -1,6 +1,6 @@
- if crop.approved? && signed_in? - 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 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
= render 'harvests/modal', harvest: Harvest.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) = render 'seeds/modal', seed: Seed.new(crop: @crop, owner: current_member)

View File

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

View File

@@ -41,14 +41,6 @@
= f.radio_button(:perennial, true, label: "Perennial") = f.radio_button(:perennial, true, label: "Perennial")
%span.help-block Living more than two years %span.help-block Living more than two years
%h2 OpenFarm Data
= 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? - unless @crop.approved?
= link_to 'Search wikipedia', "https://en.wikipedia.org/w/index.php?search=#{@crop.name}", target: '_blank' = 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' = f.url_field :en_wikipedia_url, id: "en_wikipedia_url", label: 'Wikipedia URL'

View File

@@ -12,7 +12,7 @@
#{harvest.owner} harvested #{display_quantity(harvest)}. #{harvest.owner} harvested #{display_quantity(harvest)}.
.float-right= render 'members/location', member: harvest.owner .float-right= render 'members/location', member: harvest.owner
.harvest-timeago .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' %li.list-group-item= link_to "View all #{crop.name} harvests", crop_harvests_path(crop), class: 'card-link'
- if crop.approved? - if crop.approved?
- if current_member - 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

@@ -10,6 +10,10 @@
= edit_icon = edit_icon
= t('.edit') = t('.edit')
= link_to crop_openfarm_path(crop), method: :post, class: 'dropdown-item' do
= icon 'far', 'update'
Fetch data from OpenFarm
= link_to crop_gbif_path(crop), method: :post, class: 'dropdown-item' do = link_to crop_gbif_path(crop), method: :post, class: 'dropdown-item' do
= icon 'far', 'update' = icon 'far', 'update'
Fetch data from GBIF Fetch data from GBIF

View File

@@ -74,6 +74,7 @@
.card-body .card-body
%h4 How to grow #{@crop.name.pluralize} %h4 How to grow #{@crop.name.pluralize}
= render 'grown_for', crop: @crop = render 'grown_for', crop: @crop
= render 'planting_advice', crop: @crop
- if @crop.parent - if @crop.parent
%hr/ %hr/
%p.parent-crop %p.parent-crop
@@ -111,8 +112,6 @@
= render 'harvests', crop: @crop = render 'harvests', crop: @crop
= render 'find_seeds', crop: @crop = render 'find_seeds', crop: @crop
= render 'openfarm_data', crop: @crop
= cute_icon = cute_icon
.card .card
.card-body .card-body
@@ -125,6 +124,13 @@
= icon 'fas', 'external-link-alt' = icon 'fas', 'external-link-alt'
Wikipedia (English) 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 %li.list-group-item
= link_to "https://www.gardenate.com/plant/#{CGI.escape @crop.name}", = link_to "https://www.gardenate.com/plant/#{CGI.escape @crop.name}",
target: "_blank", target: "_blank",
@@ -141,14 +147,6 @@
= icon 'fas', 'external-link-alt' = icon 'fas', 'external-link-alt'
Google Google
%li.list-group-item
= link_to 'https://chat.openai.com/?model=gpt-4o&prompt=' + CGI.escape(['How do I grow', @crop.name, "and what grows well with it? What should I plant the next season if practicing crop rotation? Explain why and add links to your sources"].join(' ')),
target: "_blank",
class: 'card-link',
rel: "noopener noreferrer" do
= icon 'fas', 'external-link-alt'
ChatGPT
%li.list-group-item %li.list-group-item
= link_to "https://wikihow.com/wikiHowTo?search=#{CGI.escape "grow #{@crop.name}" }", = link_to "https://wikihow.com/wikiHowTo?search=#{CGI.escape "grow #{@crop.name}" }",
target: "_blank", target: "_blank",

View File

@@ -15,16 +15,3 @@
method: :delete, class: "remove btn btn-danger" method: :delete, class: "remove btn btn-danger"
- else - else
= link_to 'Connect to Flickr', '/members/auth/flickr', class: 'btn' = 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) - if can?(:destroy, garden)
.dropdown-divider .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 .col-md-12
%p Nothing has been planted here. %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 .row
.col-md-2 .col-md-2
%small
%a{href: "#content"}
Skip to main content
= render 'layouts/nav', model: Garden = render 'layouts/nav', model: Garden
%label %label
= link_to show_inactive_tickbox_path('gardens', owner: @owner, show_all: @show_all) do = link_to show_inactive_tickbox_path('gardens', owner: @owner, show_all: @show_all) do
@@ -23,7 +20,7 @@
%hr/ %hr/
= render @owner = render @owner
.col-md-10#content .col-md-10
- if @gardens.empty? - if @gardens.empty?
%p There are no gardens to display. %p There are no gardens to display.
- if can?(:create, Garden) && @owner == current_member - if can?(:create, Garden) && @owner == current_member

View File

@@ -66,7 +66,8 @@
- if can?(:destroy, @garden) - if can?(:destroy, @garden)
.dropdown-divider .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 %section
%h2 Current activities in garden %h2 Current activities in garden

View File

@@ -5,8 +5,3 @@
- @matching_plantings.each do |planting| - @matching_plantings.each do |planting|
= f.radio_button :planting_id, planting.id, label: planting = f.radio_button :planting_id, planting.id, label: planting
= f.submit "save", class: 'btn btn-sm' = 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 %h3
Harvested Harvested
= editable :date, @harvest, :harvested_at, display_field: '.harvested_at' = 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 %span.harvested_at= I18n.l @harvest.harvested_at
.card{class: @harvest.quantity.present? ? '' : 'text-muted'} .card{class: @harvest.quantity.present? ? '' : 'text-muted'}

View File

@@ -4,6 +4,6 @@
member: link_to(t('.member_linktext', count: Member.confirmed.size.to_i), members_path), 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_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_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), 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'))

View File

@@ -8,14 +8,13 @@
%p= render 'stats', cached: true %p= render 'stats', cached: true
.col .col
%br
%p %p
- if current_member.plantings.active.any? - 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 = planting_icon
Track my plantings Track my plantings
%p %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 = garden_icon
Show me my garden Show me my garden
- else - else
@@ -62,16 +61,3 @@
%section.members %section.members
= cute_icon = cute_icon
= render 'members', cached: true = render 'members', cached: true
.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')

View File

@@ -6,6 +6,6 @@
%span.site-name Growstuff %span.site-name Growstuff
.nav= render 'crops/search_bar' .nav= render 'crops/search_bar'
.nav .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" } %button.navbar-toggler{ "aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", type: "button" }
%span.navbar-toggler-icon %i.fas.fa-ellipsis-v.navbar-toggler-icon
= render 'layouts/menu' = render 'layouts/menu'

View File

@@ -1,11 +1,11 @@
#navbarSupportedContent.collapse.navbar-collapse #navbarSupportedContent.collapse.navbar-collapse
%ul.navbar-nav.mr-auto.bg-dark %ul.navbar-nav.mr-auto
- if signed_in? - if signed_in?
%li.nav-item %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" = image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications"
%li.nav-item %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' = image_icon 'gardens'
%li.nav-item.dropdown %li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"} %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 - cache("everyone-menu", expires_in: 1.week) do
%li.nav-item.dropdown %li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"} %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.crops')
%span
= t('.crops')
.dropdown-menu .dropdown-menu
= link_to crops_path, class: 'dropdown-item' do = link_to crops_path, class: 'dropdown-item' do
= t('.browse_crops') = t('.browse_crops')
@@ -46,9 +44,7 @@
= harvest_icon = harvest_icon
= t('.harvests') = t('.harvests')
%li.nav-item.dropdown %li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"} %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.community')
%span
= t('.community')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"} .dropdown-menu{"aria-labelledby" => "navbarDropdown"}
= link_to t('.community_map'), places_path, class: 'dropdown-item' = link_to t('.community_map'), places_path, class: 'dropdown-item'
= link_to t('.browse_members'), members_path, class: 'dropdown-item' = link_to t('.browse_members'), members_path, class: 'dropdown-item'
@@ -58,9 +54,7 @@
- if member_signed_in? - if member_signed_in?
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin) - if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
%li.nav-item.dropdown %li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"} %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.admin')
%span
= t('.admin')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"} .dropdown-menu{"aria-labelledby" => "navbarDropdown"}
- if current_member.role?(:crop_wrangler) - if current_member.role?(:crop_wrangler)
= link_to t('.crop_wrangling'), wrangle_crops_path, class: 'dropdown-item' = 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 - cache member do
.card .card.card-double
.card-body .card-body
%h4.login-name= link_to member, member %h4.login-name= link_to member, member
%div %div

View File

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

View File

@@ -21,7 +21,7 @@
- if planting.finish_is_predicatable? - if planting.finish_is_predicatable?
.card.fact-card .card.fact-card
%h3 Progress %h3 Progress
- if planting.age_in_days.to_i < 0 - if planting.age_in_days < 0
%strong Planned %strong Planned
- else - else
%strong #{planting.age_in_days}/#{planting.expected_lifespan} %strong #{planting.age_in_days}/#{planting.expected_lifespan}
@@ -45,7 +45,7 @@
%h3 Growing %h3 Growing
%strong= seedling_icon %strong= seedling_icon
%span %span
- if planting.age_in_days.to_i < 0 - if planting.age_in_days < 0
Planting planned Planting planned
- else - else
Planting is still growing today Planting is still growing today
@@ -89,9 +89,3 @@
- if planting.finished_at.present? - if planting.finished_at.present?
%span.plantingfact--finish %span.plantingfact--finish
= planting.finished_at.year = 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 .row
.col-md-8 .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, :id, :name, required: true,
label: 'Where did you plant it?') label: 'Where did you plant it?')
= link_to "Add a garden.", new_garden_path = link_to "Add a garden.", new_garden_path
@@ -43,15 +43,6 @@
= f.select(:sunniness, Planting::SUNNINESS_VALUES, { include_blank: '', label: 'Sun or shade?' } ) = f.select(:sunniness, Planting::SUNNINESS_VALUES, { include_blank: '', label: 'Sun or shade?' } )
.col-md-4 .col-md-4
= f.number_field :quantity, label: 'How many?', min: 1 = 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' = f.text_area :description, rows: 6, label: 'Tell us more about it'
.row .row

View File

@@ -9,7 +9,7 @@
%p Which garden is the planting in? %p Which garden is the planting in?
%ul.list-group %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 %li.list-group-item
= link_to plantings_path(planting: {crop_id: planting.crop_id, garden_id: garden.id}), method: :post do = link_to plantings_path(planting: {crop_id: planting.crop_id, garden_id: garden.id}), method: :post do
.md-v-line .md-v-line

View File

@@ -7,15 +7,6 @@
= tag("meta", property: "og:type", content: "website") = tag("meta", property: "og:type", content: "website")
= tag("meta", property: "og:url", content: request.original_url) = tag("meta", property: "og:url", content: request.original_url)
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME']) = 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 - content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Plantings', plantings_path %li.breadcrumb-item= link_to 'Plantings', plantings_path
@@ -48,10 +39,10 @@
- elsif @planting.percentage_grown.present? - elsif @planting.percentage_grown.present?
#{@planting.percentage_grown.to_i}% #{@planting.percentage_grown.to_i}%
- if @planting.finish_is_predicatable? - if @planting.finish_is_predicatable?
- if @planting.age_in_days.to_i < 0 - if @planting.age_in_days < 0
%strong Planned %strong Planned
- else - 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 'timeline', planting: @planting
= render 'likes/likes', object: @planting = render 'likes/likes', object: @planting
@@ -89,11 +80,7 @@
- else - else
.col-md-12 .col-md-12
%p Nothing is currently planned here. %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 .col-md-4.col-xs-12
= render @planting.crop = render @planting.crop

View File

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

View File

@@ -49,19 +49,17 @@
.col-md-6= f.number_field :days_until_maturity_max, label_as_placeholder: true, label: 'max', prepend: 'to', append: "days", min: 1 .col-md-6= f.number_field :days_until_maturity_max, label_as_placeholder: true, label: 'max', prepend: 'to', append: "days", min: 1
.row .row
.col-md-3 .col-md-4
= f.select(:organic, Seed::ORGANIC_VALUES, { label: 'Organic?', wrapper: { class: 'required' }, required: true }, default: 'unknown') = f.select(:organic, Seed::ORGANIC_VALUES, {label: 'Organic?', wrapper: { class: 'required'}, required: true}, default: 'unknown')
.col-md-3 .col-md-4
= f.select(:gmo, Seed::GMO_VALUES, { label: 'GMO?', wrapper: { class: 'required' }, required: true }, default: 'unknown') = f.select(:gmo, Seed::GMO_VALUES, {label: 'GMO?', wrapper: { class: 'required'}, required: true}, default: 'unknown')
.col-md-3 .col-md-4
= f.select(:heirloom, Seed::HEIRLOOM_VALUES, { label: 'Heirloom?', wrapper: { class: 'required' }, required: true }, default: 'unknown') = f.select(:heirloom, Seed::HEIRLOOM_VALUES, {label: 'Heirloom?', wrapper: { class: 'required'}, required: true}, default: 'unknown')
.col-md-3
= f.select(:source, Seed::SOURCE_VALUES, { label: 'Source?', wrapper: { class: 'required' }, required: true }, default: 'unknown')
= f.text_area :description, rows: 6 = f.text_area :description, rows: 6
%hr/ %hr/
= t('.trade_help', site_name: ENV['GROWSTUFF_SITE_NAME']) = t('.trade_help', site_name: ENV['GROWSTUFF_SITE_NAME'])
= f.select(:tradable_to, Seed::TRADABLE_TO_VALUES, { label: 'Will trade', wrapper: { class: 'required' }, required: true }) = f.select(:tradable_to, Seed::TRADABLE_TO_VALUES, {label: 'Will trade', wrapper: { class: 'required'}, required: true})
%span.help_inline %span.help_inline
- if current_member.location.blank? - if current_member.location.blank?
Don't forget to Don't forget to

View File

@@ -1,2 +0,0 @@
- likeable = like.likeable
= render "timeline/likeables/#{likeable.class.name.downcase}", likeable: likeable

View File

@@ -14,7 +14,6 @@
= link_to owner, owner = link_to owner, owner
= event_description(event) = event_description(event)
= render 'timeline/photos', photo: resolve_model(event) if event.event_type == 'photo' = render 'timeline/photos', photo: resolve_model(event) if event.event_type == 'photo'
= render 'timeline/like', like: resolve_model(event) if event.event_type == 'like'
%small %small
- if event.event_at.present? - if event.event_at.present?
- if event.event_at.kind_of?(Date) - if event.event_at.kind_of?(Date)

View File

@@ -1 +0,0 @@
= render 'timeline/photos', photo: likeable

View File

@@ -1,6 +0,0 @@
.card.my-2
.card-body
%blockquote.blockquote.mb-0
%p= truncate(likeable.body, length: 140)
%footer.blockquote-footer
= link_to "view post", likeable

View File

@@ -1,10 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class UnauthorisedError < JSONAPI::Error
end
JSONAPI.configure do |config| JSONAPI.configure do |config|
# built in paginators are :none, :offset, :paged # built in paginators are :none, :offset, :paged
config.default_paginator = :offset config.default_paginator = :offset
config.default_page_size = 10 config.default_page_size = 10
config.maximum_page_size = 100 config.maximum_page_size = 100
config.exception_class_whitelist = [CanCan::AccessDenied, UnauthorisedError]
end end

View File

@@ -54,7 +54,6 @@ en:
You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm
link to finalize confirming your new email address. link to finalize confirming your new email address.
destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
api_token_regenerated: 'Your API token has been regenerated.'
unlocks: unlocks:
send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
unlocked: 'Your account has been unlocked successfully. Please sign in to continue.' unlocked: 'Your account has been unlocked successfully. Please sign in to continue.'

View File

@@ -123,8 +123,6 @@ en:
no_plantings: no plantings no_plantings: no plantings
plantingsthumbnail: plantings/thumbnail plantingsthumbnail: plantings/thumbnail
updated: Garden was successfully updated. updated: Garden was successfully updated.
confirm_delete: All plantings associated with this garden will also be deleted. Are you sure?
confirm_deactivate: All plantings associated with this garden will be marked as finished. Are you sure?
harvests: harvests:
created: Harvest was successfully created. created: Harvest was successfully created.
harvest_something: Harvest something harvest_something: Harvest something
@@ -177,6 +175,7 @@ en:
Our team includes volunteers from all walks of life and all skill levels. To get involved, Our team includes volunteers from all walks of life and all skill levels. To get involved,
visit %{talk_link} or find more information on the %{wiki_link}. visit %{talk_link} or find more information on the %{wiki_link}.
get_involved_title: Get Involved get_involved_title: Get Involved
github_linktext: Github
open_data_body_html: > open_data_body_html: >
We're building a database of crops, planting advice, seed sources, and other information that anyone We're building a database of crops, planting advice, seed sources, and other information that anyone
can use for free, under a %{creative_commons_link}. You can use this data for research, to build apps, can use for free, under a %{creative_commons_link}. You can use this data for research, to build apps,
@@ -208,15 +207,10 @@ en:
view_all: View all seeds view_all: View all seeds
stats: stats:
member_linktext: "%{count} members" member_linktext: "%{count} members"
message_html: So far, %{member} have planted %{number_crops} %{number_plantings} in %{number_gardens}; and %{contributors} people have contributed to our code on %{github}! message_html: So far, %{member} have planted %{number_crops} %{number_plantings} in %{number_gardens}.
number_crops_linktext: "%{count} crops" number_crops_linktext: "%{count} crops"
number_gardens_linktext: "%{count} gardens" number_gardens_linktext: "%{count} gardens"
number_plantings_linktext: "%{count} times" number_plantings_linktext: "%{count} times"
pwa_android_steps_html: 1. Tap the three dots in the top right corner of Chrome.<br>2. Tap <strong>Install app</strong> or <strong>Add to Home screen</strong>.
pwa_android_title: For Android
pwa_ios_steps_html: 1. Tap the <strong>Share</strong> button in Safari.<br>2. Scroll down and tap <strong>Add to Home Screen</strong>'.
pwa_ios_title: For iOS (iPhone/iPad)
pwa_title: Want to install Growstuff on your phone?
label: label:
days_until_harvest: "%{number} days" days_until_harvest: "%{number} days"
weeks_until_harvest: "%{number} weeks until harvest" weeks_until_harvest: "%{number} weeks until harvest"

View File

@@ -16,7 +16,6 @@ Rails.application.routes.draw do
} }
devise_scope :member do devise_scope :member do
get '/members/unsubscribe/:message' => 'members#unsubscribe', as: 'unsubscribe_member' get '/members/unsubscribe/:message' => 'members#unsubscribe', as: 'unsubscribe_member'
post '/members/regenerate_api_token' => 'registrations#regenerate_api_token', as: 'regenerate_api_token'
end end
match '/members/:id/finish_signup' => 'members#finish_signup', via: %i(get patch), as: :finish_signup match '/members/:id/finish_signup' => 'members#finish_signup', via: %i(get patch), as: :finish_signup
@@ -82,6 +81,7 @@ Rails.application.routes.draw do
get 'sunniness' => 'charts/crops#sunniness', constraints: { format: 'json' } get 'sunniness' => 'charts/crops#sunniness', constraints: { format: 'json' }
get 'planted_from' => 'charts/crops#planted_from', constraints: { format: 'json' } get 'planted_from' => 'charts/crops#planted_from', constraints: { format: 'json' }
get 'harvested_for' => 'charts/crops#harvested_for', constraints: { format: 'json' } get 'harvested_for' => 'charts/crops#harvested_for', constraints: { format: 'json' }
post :openfarm
post :gbif post :gbif
collection do collection do
@@ -142,7 +142,6 @@ Rails.application.routes.draw do
namespace :api do namespace :api do
namespace :v1 do namespace :v1 do
jsonapi_resources :activities
jsonapi_resources :crops jsonapi_resources :crops
jsonapi_resources :gardens jsonapi_resources :gardens
jsonapi_resources :harvests jsonapi_resources :harvests

View File

@@ -1,10 +0,0 @@
class AddFieldsToCrops < ActiveRecord::Migration[5.2]
def change
add_column :crops, :row_spacing, :integer
add_column :crops, :spread, :integer
add_column :crops, :height, :integer
add_column :crops, :sowing_method, :string
add_column :crops, :sun_requirements, :string
add_column :crops, :growing_degree_days, :integer
end
end

View File

@@ -1,21 +0,0 @@
class PopulateCropFieldsFromOpenfarmData < ActiveRecord::Migration[5.2]
def up
Crop.find_each do |crop|
if crop.openfarm_data.present?
attributes = crop.openfarm_data.fetch('attributes', {})
crop.update_columns(
row_spacing: attributes['row_spacing'],
spread: attributes['spread'],
height: attributes['height'],
sowing_method: attributes['sowing_method'],
sun_requirements: attributes['sun_requirements'],
growing_degree_days: attributes['growing_degree_days']
)
end
end
end
def down
# This migration is not reversible.
end
end

View File

@@ -2,6 +2,6 @@
class AddLanguageToAlternateNames < ActiveRecord::Migration[7.2] class AddLanguageToAlternateNames < ActiveRecord::Migration[7.2]
def change def change
add_column :alternate_names, :language, :string add_column :alternate_names, :language, :string, null: false
end end
end end

View File

@@ -1,6 +0,0 @@
class AddSourceToSeeds < ActiveRecord::Migration[7.2]
def change
add_column :seeds, :source, :string
add_index :seeds, :source
end
end

View File

@@ -1,52 +0,0 @@
class AddIndexesCrops < ActiveRecord::Migration[7.2]
def change
add_index :alternate_names, :crop_id
add_index :alternate_names, :creator_id
add_index :alternate_names, :language
add_index :comments, :author_id
add_index :crop_companions, %i(crop_a_id crop_b_id)
add_index :crops, :creator_id
add_index :crops, :parent_id
add_index :follows, %i(follower_id followed_id)
add_index :forums, :owner_id
add_index :harvests, :crop_id
add_index :harvests, :owner_id
add_index :harvests, :plant_part_id
add_index :members_roles, %i(member_id role_id)
add_index :notifications, :sender_id
add_index :notifications, :recipient_id
add_index :orders_products, %i(order_id product_id)
add_index :photo_associations, :crop_id # TODO: Is this still in use?
add_index :photos, :owner_id
add_index :photos, :source_id
add_index :photos_plantings, %i(photo_id planting_id)
add_index :plant_parts, :slug, unique: true
add_index :plantings, :crop_id
add_index :plantings, :garden_id
add_index :plantings, :owner_id
add_index :plantings, :parent_seed_id
add_index :posts, :forum_id
add_index :scientific_names, :crop_id
add_index :scientific_names, :creator_id
add_index :seeds, :owner_id
add_index :seeds, :crop_id
add_index :seeds, :parent_planting_id
end
end

View File

@@ -1,5 +0,0 @@
class AddOverallRatingPlantings < ActiveRecord::Migration[7.2]
def change
add_column :plantings, :overall_rating, :integer
end
end

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -68,9 +68,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.datetime "created_at", precision: nil t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.string "language" t.string "language"
t.index ["creator_id"], name: "index_alternate_names_on_creator_id"
t.index ["crop_id"], name: "index_alternate_names_on_crop_id"
t.index ["language"], name: "index_alternate_names_on_language"
end end
create_table "authentications", id: :serial, force: :cascade do |t| create_table "authentications", id: :serial, force: :cascade do |t|
@@ -212,8 +209,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.datetime "created_at", precision: nil t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.string "commentable_type" t.string "commentable_type"
t.index ["author_id"], name: "index_comments_on_author_id"
t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable_type_and_commentable_id"
end end
create_table "crop_companions", force: :cascade do |t| create_table "crop_companions", force: :cascade do |t|
@@ -221,7 +216,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.integer "crop_b_id", null: false t.integer "crop_b_id", null: false
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.index ["crop_a_id", "crop_b_id"], name: "index_crop_companions_on_crop_a_id_and_crop_b_id"
end end
create_table "crop_posts", id: false, force: :cascade do |t| create_table "crop_posts", id: false, force: :cascade do |t|
@@ -252,15 +246,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.jsonb "openfarm_data" t.jsonb "openfarm_data"
t.integer "harvests_count", default: 0 t.integer "harvests_count", default: 0
t.integer "photo_associations_count", default: 0 t.integer "photo_associations_count", default: 0
t.integer "row_spacing"
t.integer "spread"
t.integer "height"
t.string "sowing_method"
t.string "sun_requirements"
t.integer "growing_degree_days"
t.index ["creator_id"], name: "index_crops_on_creator_id"
t.index ["name"], name: "index_crops_on_name" t.index ["name"], name: "index_crops_on_name"
t.index ["parent_id"], name: "index_crops_on_parent_id"
t.index ["requester_id"], name: "index_crops_on_requester_id" t.index ["requester_id"], name: "index_crops_on_requester_id"
t.index ["slug"], name: "index_crops_on_slug", unique: true t.index ["slug"], name: "index_crops_on_slug", unique: true
end end
@@ -270,7 +256,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.integer "followed_id" t.integer "followed_id"
t.datetime "created_at", precision: nil t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.index ["follower_id", "followed_id"], name: "index_follows_on_follower_id_and_followed_id"
end end
create_table "forums", id: :serial, force: :cascade do |t| create_table "forums", id: :serial, force: :cascade do |t|
@@ -280,7 +265,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.datetime "created_at", precision: nil t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.string "slug" t.string "slug"
t.index ["owner_id"], name: "index_forums_on_owner_id"
t.index ["slug"], name: "index_forums_on_slug", unique: true t.index ["slug"], name: "index_forums_on_slug", unique: true
end end
@@ -344,9 +328,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.float "si_weight" t.float "si_weight"
t.integer "planting_id" t.integer "planting_id"
t.integer "likes_count", default: 0 t.integer "likes_count", default: 0
t.index ["crop_id"], name: "index_harvests_on_crop_id"
t.index ["owner_id"], name: "index_harvests_on_owner_id"
t.index ["plant_part_id"], name: "index_harvests_on_plant_part_id"
t.index ["planting_id"], name: "index_harvests_on_planting_id" t.index ["planting_id"], name: "index_harvests_on_planting_id"
end end
@@ -483,7 +464,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
create_table "members_roles", id: false, force: :cascade do |t| create_table "members_roles", id: false, force: :cascade do |t|
t.integer "member_id" t.integer "member_id"
t.integer "role_id" t.integer "role_id"
t.index ["member_id", "role_id"], name: "index_members_roles_on_member_id_and_role_id"
end end
create_table "notifications", id: :serial, force: :cascade do |t| create_table "notifications", id: :serial, force: :cascade do |t|
@@ -497,14 +477,11 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.string "notifiable_type" t.string "notifiable_type"
t.index ["notifiable_type", "notifiable_id"], name: "index_notifications_on_notifiable_type_and_notifiable_id" t.index ["notifiable_type", "notifiable_id"], name: "index_notifications_on_notifiable_type_and_notifiable_id"
t.index ["recipient_id"], name: "index_notifications_on_recipient_id"
t.index ["sender_id"], name: "index_notifications_on_sender_id"
end end
create_table "orders_products", id: false, force: :cascade do |t| create_table "orders_products", id: false, force: :cascade do |t|
t.integer "order_id" t.integer "order_id"
t.integer "product_id" t.integer "product_id"
t.index ["order_id", "product_id"], name: "index_orders_products_on_order_id_and_product_id"
end end
create_table "photo_associations", id: :serial, force: :cascade do |t| create_table "photo_associations", id: :serial, force: :cascade do |t|
@@ -514,7 +491,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.integer "crop_id" t.integer "crop_id"
t.index ["crop_id"], name: "index_photo_associations_on_crop_id"
t.index ["photographable_id", "photographable_type", "photo_id"], name: "items_to_photos_idx", unique: true t.index ["photographable_id", "photographable_type", "photo_id"], name: "items_to_photos_idx", unique: true
t.index ["photographable_id", "photographable_type"], name: "photographable_idx" t.index ["photographable_id", "photographable_type"], name: "photographable_idx"
end end
@@ -535,15 +511,12 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.string "source" t.string "source"
t.integer "comments_count", default: 0 t.integer "comments_count", default: 0
t.index ["fullsize_url"], name: "index_photos_on_fullsize_url", unique: true t.index ["fullsize_url"], name: "index_photos_on_fullsize_url", unique: true
t.index ["owner_id"], name: "index_photos_on_owner_id"
t.index ["source_id"], name: "index_photos_on_source_id"
t.index ["thumbnail_url"], name: "index_photos_on_thumbnail_url", unique: true t.index ["thumbnail_url"], name: "index_photos_on_thumbnail_url", unique: true
end end
create_table "photos_plantings", id: false, force: :cascade do |t| create_table "photos_plantings", id: false, force: :cascade do |t|
t.integer "photo_id" t.integer "photo_id"
t.integer "planting_id" t.integer "planting_id"
t.index ["photo_id", "planting_id"], name: "index_photos_plantings_on_photo_id_and_planting_id"
end end
create_table "photos_seeds", id: false, force: :cascade do |t| create_table "photos_seeds", id: false, force: :cascade do |t|
@@ -558,7 +531,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.string "slug" t.string "slug"
t.integer "harvests_count", default: 0 t.integer "harvests_count", default: 0
t.index ["slug"], name: "index_plant_parts_on_slug", unique: true
end end
create_table "plantings", id: :serial, force: :cascade do |t| create_table "plantings", id: :serial, force: :cascade do |t|
@@ -582,11 +554,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.integer "harvests_count", default: 0 t.integer "harvests_count", default: 0
t.integer "likes_count", default: 0 t.integer "likes_count", default: 0
t.boolean "failed", default: false, null: false t.boolean "failed", default: false, null: false
t.integer "overall_rating"
t.index ["crop_id"], name: "index_plantings_on_crop_id"
t.index ["garden_id"], name: "index_plantings_on_garden_id"
t.index ["owner_id"], name: "index_plantings_on_owner_id"
t.index ["parent_seed_id"], name: "index_plantings_on_parent_seed_id"
t.index ["slug"], name: "index_plantings_on_slug", unique: true t.index ["slug"], name: "index_plantings_on_slug", unique: true
end end
@@ -601,7 +568,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.integer "likes_count", default: 0 t.integer "likes_count", default: 0
t.integer "comments_count", default: 0 t.integer "comments_count", default: 0
t.index ["created_at", "author_id"], name: "index_posts_on_created_at_and_author_id" t.index ["created_at", "author_id"], name: "index_posts_on_created_at_and_author_id"
t.index ["forum_id"], name: "index_posts_on_forum_id"
t.index ["slug"], name: "index_posts_on_slug", unique: true t.index ["slug"], name: "index_posts_on_slug", unique: true
end end
@@ -624,8 +590,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.string "gbif_rank" t.string "gbif_rank"
t.string "gbif_status" t.string "gbif_status"
t.string "wikidata_id" t.string "wikidata_id"
t.index ["creator_id"], name: "index_scientific_names_on_creator_id"
t.index ["crop_id"], name: "index_scientific_names_on_crop_id"
end end
create_table "seeds", id: :serial, force: :cascade do |t| create_table "seeds", id: :serial, force: :cascade do |t|
@@ -647,12 +611,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do
t.date "finished_at" t.date "finished_at"
t.integer "parent_planting_id" t.integer "parent_planting_id"
t.date "saved_at" t.date "saved_at"
t.string "source"
t.index ["crop_id"], name: "index_seeds_on_crop_id"
t.index ["owner_id"], name: "index_seeds_on_owner_id"
t.index ["parent_planting_id"], name: "index_seeds_on_parent_planting_id"
t.index ["slug"], name: "index_seeds_on_slug", unique: true t.index ["slug"], name: "index_seeds_on_slug", unique: true
t.index ["source"], name: "index_seeds_on_source"
end end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"

View File

@@ -173,8 +173,7 @@ def load_test_users
organic: select_random_item(Seed::ORGANIC_VALUES), organic: select_random_item(Seed::ORGANIC_VALUES),
gmo: select_random_item(['certified GMO-free', 'non-certified GMO-free', 'GMO', 'unknown']), # Strangely, this doesn't want to work as Seed:GMO_VALUES gmo: select_random_item(['certified GMO-free', 'non-certified GMO-free', 'GMO', 'unknown']), # Strangely, this doesn't want to work as Seed:GMO_VALUES
heirloom: select_random_item(Seed::HEIRLOOM_VALUES), heirloom: select_random_item(Seed::HEIRLOOM_VALUES),
parent_planting: @user.plantings.first, parent_planting: @user.plantings.first
finished: false
) )
photo = Photo.create!( photo = Photo.create!(

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