mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-04-11 18:38:50 -04:00
@@ -1,7 +1,7 @@
|
||||
FROM mcr.microsoft.com/devcontainers/ruby:0-3.1-bullseye
|
||||
|
||||
# Install Rails
|
||||
RUN gem install rails:7.0.7 webdrivers:5.2.0
|
||||
RUN gem install rails:7.0.8
|
||||
|
||||
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
|
||||
# The value is a comma-separated list of allowed domains
|
||||
@@ -12,8 +12,12 @@ ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev,.preview.app.github.dev,.app.git
|
||||
#RUN bundle exec rake db:migrate
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install --no-install-recommends bash-completion
|
||||
|
||||
# Chrome for testing packages. https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-doesnt-launch-on-linux
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install --no-install-recommends ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils
|
||||
|
||||
# [Optional] Uncomment this line to install additional gems.
|
||||
# RUN gem install <your-gem-names-here>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// This can be used to network with other containers or the host.
|
||||
"forwardPorts": [3000, 5432, 9200],
|
||||
"forwardPorts": [3000, 5432, 9200, 8081],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// these don't actually work as postCreateCommands, you need to run them manually
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -76,7 +76,7 @@ jobs:
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Setup yarn cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
||||
@@ -7,7 +7,7 @@ AllCops:
|
||||
Exclude:
|
||||
- 'db/schema.rb'
|
||||
- 'vendor/**/*'
|
||||
TargetRailsVersion: 6.0
|
||||
TargetRailsVersion: 7.0
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
||||
@@ -48,12 +48,6 @@ FactoryBot/CreateList:
|
||||
- 'spec/views/places/show.html.haml_spec.rb'
|
||||
- 'spec/views/posts/index.html.haml_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
FactoryBot/RedundantFactoryOption:
|
||||
Exclude:
|
||||
- 'spec/factories/scientific_name.rb'
|
||||
|
||||
# Offense count: 1135
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
FactoryBot/SyntaxMethods:
|
||||
@@ -101,15 +95,6 @@ Layout/LineEndStringConcatenationIndentation:
|
||||
Layout/LineLength:
|
||||
Max: 304
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
|
||||
# SupportedStyles: space, no_space
|
||||
# SupportedStylesForEmptyBraces: space, no_space
|
||||
Layout/SpaceBeforeBlockBraces:
|
||||
Exclude:
|
||||
- 'spec/models/photo_spec.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Lint/AmbiguousOperatorPrecedence:
|
||||
@@ -242,15 +227,6 @@ RSpec/EmptyExampleGroup:
|
||||
- 'spec/views/photos/edit.html.haml_spec.rb'
|
||||
- 'spec/views/posts/_single.html.haml_spec.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
RSpec/EmptyLineAfterExampleGroup:
|
||||
Exclude:
|
||||
- 'spec/features/crops/creating_a_crop_spec.rb'
|
||||
- 'spec/features/likeable_spec.rb'
|
||||
- 'spec/models/crop_spec.rb'
|
||||
- 'spec/support/feature_helpers.rb'
|
||||
|
||||
# Offense count: 134
|
||||
# Configuration parameters: CountAsOne.
|
||||
RSpec/ExampleLength:
|
||||
@@ -323,13 +299,6 @@ RSpec/MessageChain:
|
||||
RSpec/MessageSpies:
|
||||
EnforcedStyle: receive
|
||||
|
||||
# Offense count: 22
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: hash, symbol
|
||||
RSpec/MetadataStyle:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
RSpec/MultipleDescribes:
|
||||
Exclude:
|
||||
|
||||
@@ -97,5 +97,6 @@ submit the change with your pull request.
|
||||
## Bots
|
||||
|
||||
### Security and Dependency Updates
|
||||
- `codefactor-io[bot]`
|
||||
- DeppBot / [deppbot](https://github.com/deppbot)
|
||||
- `dependabot[bot]` [dependabot](https://github.com/dependabot-bot) / [dependabot-preview](https://github.com/apps/dependabot-preview)
|
||||
|
||||
26
Gemfile
26
Gemfile
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
|
||||
# Match ruby version in .ruby-version
|
||||
ruby File.read('.ruby-version')
|
||||
|
||||
gem 'rails', '~> 7.0.7'
|
||||
gem 'rails', '~> 7.1.0'
|
||||
|
||||
# Keeping old sprockets
|
||||
# https://github.com/rails/sprockets-rails/issues/444#issuecomment-637817050
|
||||
@@ -33,18 +33,18 @@ gem 'material_icons'
|
||||
# icons
|
||||
gem 'font-awesome-sass'
|
||||
|
||||
gem 'uglifier' # JavaScript compressor
|
||||
gem 'terser'
|
||||
|
||||
gem 'oj' # Speeds up json
|
||||
|
||||
# planting and harvest predictions
|
||||
# based on median values for the crop
|
||||
gem 'active_median', '0.2.0'
|
||||
gem 'active_median'
|
||||
gem 'active_record_union'
|
||||
|
||||
gem 'flickraw'
|
||||
gem 'jquery-rails'
|
||||
gem 'jquery-ui-rails'
|
||||
gem 'jquery-ui-rails', github: 'jquery-ui-rails/jquery-ui-rails', tag: 'v7.0.0' # See https://github.com/jquery-ui-rails/jquery-ui-rails/issues/146
|
||||
|
||||
gem 'cancancan' # for checking member privileges
|
||||
gem 'csv_shaper' # CSV export
|
||||
@@ -100,7 +100,7 @@ gem 'omniauth-twitter'
|
||||
gem "chartkick"
|
||||
|
||||
# clever elastic search
|
||||
gem 'elasticsearch', '< 7.0.0'
|
||||
gem 'elasticsearch', '~> 7.0.0'
|
||||
gem 'searchkick'
|
||||
|
||||
gem "hashie", ">= 3.5.3"
|
||||
@@ -124,10 +124,19 @@ gem 'rack-protection', '>= 2.0.1'
|
||||
gem 'mailboxer', '>= 0.15.1'
|
||||
|
||||
gem 'faraday'
|
||||
gem 'faraday_middleware'
|
||||
|
||||
gem 'rack-cors'
|
||||
|
||||
gem 'icalendar'
|
||||
|
||||
# for signups as requested by email service
|
||||
gem 'recaptcha'
|
||||
|
||||
# External APIs for data
|
||||
gem "gbifrb"
|
||||
|
||||
gem "msgpack"
|
||||
|
||||
group :production do
|
||||
gem 'bonsai-elasticsearch-rails' # Integration with Bonsa-Elasticsearch on heroku
|
||||
gem 'dalli'
|
||||
@@ -153,6 +162,7 @@ group :development, :test do
|
||||
gem 'factory_bot_rails' # for creating test data
|
||||
gem 'faker'
|
||||
gem 'haml-rails' # HTML templating language
|
||||
gem 'pry'
|
||||
gem 'query_diet'
|
||||
gem 'rspec-activemodel-mocks'
|
||||
gem 'rspec-rails' # unit testing framework
|
||||
@@ -172,11 +182,13 @@ group :development, :test do
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'axe-core-capybara'
|
||||
gem 'axe-core-rspec'
|
||||
gem 'codeclimate-test-reporter', require: false
|
||||
gem 'rails-controller-testing'
|
||||
gem 'selenium-webdriver'
|
||||
gem 'timecop'
|
||||
gem 'webdrivers'
|
||||
gem 'vcr'
|
||||
end
|
||||
|
||||
group :travis do
|
||||
|
||||
391
Gemfile.lock
391
Gemfile.lock
@@ -1,3 +1,11 @@
|
||||
GIT
|
||||
remote: https://github.com/jquery-ui-rails/jquery-ui-rails.git
|
||||
revision: 413265e81f790f795239e07e7e25e01429b2f18d
|
||||
tag: v7.0.0
|
||||
specs:
|
||||
jquery-ui-rails (7.0.0)
|
||||
railties (>= 3.2.16)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/restarone/comfortable-mexican-sofa.git
|
||||
revision: ccf9415ae220453a199759b8ecbb8e9436c75c85
|
||||
@@ -25,86 +33,110 @@ GEM
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actioncable (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activestorage (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actionmailer (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
actionview (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.1.3)
|
||||
actionview (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
actiontext (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activestorage (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actionview (7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
active_link_to (1.0.5)
|
||||
actionpack
|
||||
addressable
|
||||
active_median (0.2.0)
|
||||
activerecord (>= 4.2)
|
||||
active_median (0.4.1)
|
||||
activesupport (>= 6.1)
|
||||
active_record_union (1.3.0)
|
||||
activerecord (>= 4.0)
|
||||
active_utils (3.4.1)
|
||||
activesupport (>= 4.2)
|
||||
i18n
|
||||
activejob (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activejob (7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activerecord (7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activestorage (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activemodel (7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
activerecord (7.1.3)
|
||||
activemodel (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.8)
|
||||
activesupport (7.1.3)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.5)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
autoprefixer-rails (10.4.7.0)
|
||||
execjs (~> 2)
|
||||
axe-core-api (4.8.1)
|
||||
dumb_delegator
|
||||
virtus
|
||||
axe-core-capybara (4.8.1)
|
||||
axe-core-api
|
||||
dumb_delegator
|
||||
axe-core-rspec (4.8.1)
|
||||
axe-core-api
|
||||
dumb_delegator
|
||||
virtus
|
||||
axiom-types (0.1.1)
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
base64 (0.2.0)
|
||||
bcrypt (3.1.19)
|
||||
better_errors (2.10.1)
|
||||
erubi (>= 1.0.0)
|
||||
@@ -117,7 +149,7 @@ GEM
|
||||
erubi (~> 1.4)
|
||||
parser (>= 2.4)
|
||||
smart_properties
|
||||
bigdecimal (3.1.4)
|
||||
bigdecimal (3.1.6)
|
||||
bluecloth (2.2.0)
|
||||
bonsai-elasticsearch-rails (7.0.1)
|
||||
elasticsearch-model (< 8)
|
||||
@@ -132,16 +164,16 @@ GEM
|
||||
actionpack (>= 5.2)
|
||||
activemodel (>= 5.2)
|
||||
builder (3.2.4)
|
||||
bullet (7.1.4)
|
||||
bullet (7.1.6)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
byebug (11.1.3)
|
||||
cancancan (3.5.0)
|
||||
capybara (3.39.2)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
nokogiri (~> 1.11)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
@@ -152,17 +184,19 @@ GEM
|
||||
capybara-screenshot (1.0.26)
|
||||
capybara (>= 1.0, < 4)
|
||||
launchy
|
||||
carrierwave (2.1.1)
|
||||
activemodel (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
carrierwave (3.0.5)
|
||||
activemodel (>= 6.0.0)
|
||||
activesupport (>= 6.0.0)
|
||||
addressable (~> 2.6)
|
||||
image_processing (~> 1.1)
|
||||
mimemagic (>= 0.3.0)
|
||||
mini_mime (>= 0.1.3)
|
||||
marcel (~> 1.0.0)
|
||||
ssrf_filter (~> 1.0)
|
||||
chartkick (5.0.5)
|
||||
codeclimate-test-reporter (1.0.9)
|
||||
simplecov (<= 0.13)
|
||||
coderay (1.1.3)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
coffee-rails (5.0.0)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 5.2.0)
|
||||
@@ -172,19 +206,22 @@ GEM
|
||||
coffee-script-source (1.12.2)
|
||||
comfy_bootstrap_form (4.0.9)
|
||||
rails (>= 5.0.0)
|
||||
concurrent-ruby (1.2.2)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
crass (1.0.6)
|
||||
csv_shaper (1.3.2)
|
||||
activesupport (>= 3.0.0)
|
||||
dalli (3.2.6)
|
||||
dalli (3.2.7)
|
||||
base64
|
||||
database_cleaner (2.0.2)
|
||||
database_cleaner-active_record (>= 2, < 3)
|
||||
database_cleaner-active_record (2.1.0)
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.3)
|
||||
date (3.3.4)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (4.9.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
@@ -199,61 +236,44 @@ GEM
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
elasticsearch (6.8.3)
|
||||
elasticsearch-api (= 6.8.3)
|
||||
elasticsearch-transport (= 6.8.3)
|
||||
elasticsearch-api (6.8.3)
|
||||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
dumb_delegator (1.0.0)
|
||||
elasticsearch (7.0.0)
|
||||
elasticsearch-api (= 7.0.0)
|
||||
elasticsearch-transport (= 7.0.0)
|
||||
elasticsearch-api (7.0.0)
|
||||
multi_json
|
||||
elasticsearch-model (7.1.1)
|
||||
activesupport (> 3)
|
||||
elasticsearch (> 1)
|
||||
hashie
|
||||
elasticsearch-rails (7.1.0)
|
||||
elasticsearch-transport (6.8.3)
|
||||
faraday (~> 1)
|
||||
elasticsearch-transport (7.0.0)
|
||||
faraday
|
||||
multi_json
|
||||
erubi (1.12.0)
|
||||
erubis (2.7.0)
|
||||
excon (0.93.1)
|
||||
excon (0.109.0)
|
||||
execjs (2.8.1)
|
||||
factory_bot (6.4.2)
|
||||
factory_bot (6.4.5)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.4.2)
|
||||
factory_bot_rails (6.4.3)
|
||||
factory_bot (~> 6.4)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.2.2)
|
||||
faker (3.2.3)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
ffi (1.15.5)
|
||||
faraday (2.9.0)
|
||||
faraday-net_http (>= 2.0, < 3.2)
|
||||
faraday-net_http (3.1.0)
|
||||
net-http
|
||||
ffi (1.16.3)
|
||||
flickraw (0.9.10)
|
||||
font-awesome-sass (5.15.1)
|
||||
sassc (>= 1.11)
|
||||
friendly_id (5.5.1)
|
||||
activerecord (>= 4.0.0)
|
||||
gbifrb (0.2.0)
|
||||
geocoder (1.8.2)
|
||||
gibbon (1.2.1)
|
||||
httparty
|
||||
@@ -263,8 +283,9 @@ GEM
|
||||
gravatar-ultimate (2.0.0)
|
||||
activesupport (>= 2.3.14)
|
||||
rack
|
||||
haml (5.2.2)
|
||||
temple (>= 0.8.0)
|
||||
haml (6.3.0)
|
||||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
haml-i18n-extractor (0.5.9)
|
||||
activesupport
|
||||
@@ -277,8 +298,8 @@ GEM
|
||||
activesupport (>= 5.1)
|
||||
haml (>= 4.0.6)
|
||||
railties (>= 5.1)
|
||||
haml_lint (0.52.0)
|
||||
haml (>= 4.0)
|
||||
haml_lint (0.55.0)
|
||||
haml (>= 5.0)
|
||||
parallel (~> 1.10)
|
||||
rainbow
|
||||
rubocop (>= 1.0)
|
||||
@@ -307,15 +328,21 @@ GEM
|
||||
rails-i18n
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
terminal-table (>= 1.5.1)
|
||||
icalendar (2.10.1)
|
||||
ice_cube (~> 0.16)
|
||||
ice_cube (0.16.4)
|
||||
ice_nine (0.11.2)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.7.2)
|
||||
irb (1.11.1)
|
||||
rdoc
|
||||
reline (>= 0.4.2)
|
||||
jquery-rails (4.6.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-ui-rails (6.0.1)
|
||||
railties (>= 3.2.16)
|
||||
json (2.7.1)
|
||||
json-schema (4.1.1)
|
||||
addressable (>= 2.8)
|
||||
@@ -364,25 +391,28 @@ GEM
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.5)
|
||||
minitest (5.20.0)
|
||||
minitest (5.21.2)
|
||||
moneta (1.0.0)
|
||||
msgpack (1.7.2)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.2.3)
|
||||
net-imap (0.3.7)
|
||||
mutex_m (0.2.0)
|
||||
net-http (0.4.1)
|
||||
uri
|
||||
net-imap (0.4.9.1)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.1)
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.3.3)
|
||||
net-smtp (0.4.0.1)
|
||||
net-protocol
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.15.5)
|
||||
nio4r (2.7.0)
|
||||
nokogiri (1.16.0)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.5-x86_64-linux)
|
||||
nokogiri (1.16.0-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
oauth (0.5.6)
|
||||
oj (3.16.3)
|
||||
@@ -401,43 +431,54 @@ GEM
|
||||
rack
|
||||
orm_adapter (0.5.0)
|
||||
parallel (1.24.0)
|
||||
parser (3.2.2.4)
|
||||
parser (3.3.0.5)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
percy-capybara (5.0.0)
|
||||
capybara (>= 3)
|
||||
pg (1.5.4)
|
||||
platform-api (3.5.0)
|
||||
platform-api (3.6.0)
|
||||
heroics (~> 0.1.1)
|
||||
moneta (~> 1.0.0)
|
||||
rate_throttle_client (~> 0.1.0)
|
||||
popper_js (1.16.1)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (5.0.4)
|
||||
puma (6.4.0)
|
||||
puma (6.4.2)
|
||||
nio4r (~> 2.0)
|
||||
query_diet (0.7.1)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8)
|
||||
rack-cors (2.0.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (3.1.0)
|
||||
rack-protection (3.2.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-session (1.0.2)
|
||||
rack (< 3)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (7.0.8)
|
||||
actioncable (= 7.0.8)
|
||||
actionmailbox (= 7.0.8)
|
||||
actionmailer (= 7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actiontext (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
rackup (1.0.0)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (7.1.3)
|
||||
actioncable (= 7.1.3)
|
||||
actionmailbox (= 7.1.3)
|
||||
actionmailer (= 7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
actiontext (= 7.1.3)
|
||||
actionview (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activemodel (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activestorage (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.8)
|
||||
railties (= 7.1.3)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
@@ -457,13 +498,14 @@ GEM
|
||||
rails_stdout_logging
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
method_source
|
||||
railties (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
irb
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
zeitwerk (~> 2.5)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
raindrops (0.20.0)
|
||||
rake (13.1.0)
|
||||
@@ -471,9 +513,14 @@ GEM
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis-client (0.18.0)
|
||||
rdoc (6.6.2)
|
||||
psych (>= 4.0.0)
|
||||
recaptcha (5.16.0)
|
||||
redis-client (0.19.1)
|
||||
connection_pool
|
||||
regexp_parser (2.8.3)
|
||||
regexp_parser (2.9.0)
|
||||
reline (0.4.2)
|
||||
io-console (~> 0.5)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
@@ -495,7 +542,7 @@ GEM
|
||||
rspec-mocks (3.12.6)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.1.0)
|
||||
rspec-rails (6.1.1)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
@@ -518,11 +565,11 @@ GEM
|
||||
rswag-ui (2.13.0)
|
||||
actionpack (>= 3.1, < 7.2)
|
||||
railties (>= 3.1, < 7.2)
|
||||
rubocop (1.59.0)
|
||||
rubocop (1.60.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.2.4)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
@@ -531,22 +578,22 @@ GEM
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.30.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.19.0)
|
||||
rubocop-capybara (2.20.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.24.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-rails (2.23.0)
|
||||
rubocop-factory_bot (2.25.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-rails (2.23.1)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-rspec (2.25.0)
|
||||
rubocop-rspec (2.26.1)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-units (4.0.1)
|
||||
ruby-vips (2.1.4)
|
||||
ruby-vips (2.2.0)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
@@ -565,19 +612,19 @@ GEM
|
||||
tilt
|
||||
scout_apm (5.3.5)
|
||||
parser
|
||||
searchkick (4.6.3)
|
||||
activemodel (>= 5)
|
||||
elasticsearch (>= 6, < 7.14)
|
||||
searchkick (5.3.1)
|
||||
activemodel (>= 6.1)
|
||||
hashie
|
||||
selenium-webdriver (4.10.0)
|
||||
selenium-webdriver (4.17.0)
|
||||
base64 (~> 0.2)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
sidekiq (7.2.0)
|
||||
sidekiq (7.2.1)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.14.0)
|
||||
redis-client (>= 0.19.0)
|
||||
simplecov (0.13.0)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
@@ -591,40 +638,44 @@ GEM
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
ssrf_filter (1.0.7)
|
||||
ssrf_filter (1.1.2)
|
||||
stringio (3.1.0)
|
||||
sysexits (1.2.0)
|
||||
temple (0.10.3)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
terser (1.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
thor (1.3.0)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.3.0)
|
||||
timecop (0.9.8)
|
||||
timeout (0.4.0)
|
||||
timeout (0.4.1)
|
||||
trollop (1.16.2)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (2.5.0)
|
||||
unicorn (6.1.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
uniform_notifier (1.16.0)
|
||||
uri (0.13.0)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
vcr (6.2.0)
|
||||
virtus (2.0.0)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webdrivers (5.3.1)
|
||||
nokogiri (~> 1.6)
|
||||
rubyzip (>= 1.3.0)
|
||||
selenium-webdriver (~> 4.0, < 4.11)
|
||||
webrat (0.7.3)
|
||||
nokogiri (>= 1.2.0)
|
||||
rack (>= 1.0)
|
||||
rack-test (>= 0.5.3)
|
||||
webrick (1.8.1)
|
||||
websocket (1.2.9)
|
||||
websocket (1.2.10)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
@@ -642,9 +693,11 @@ PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
active_median (= 0.2.0)
|
||||
active_median
|
||||
active_record_union
|
||||
active_utils
|
||||
axe-core-capybara
|
||||
axe-core-rspec
|
||||
better_errors
|
||||
bluecloth
|
||||
bonsai-elasticsearch-rails
|
||||
@@ -668,14 +721,14 @@ DEPENDENCIES
|
||||
devise
|
||||
discard (>= 1.2)
|
||||
dotenv-rails
|
||||
elasticsearch (< 7.0.0)
|
||||
elasticsearch (~> 7.0.0)
|
||||
factory_bot_rails
|
||||
faker
|
||||
faraday
|
||||
faraday_middleware
|
||||
flickraw
|
||||
font-awesome-sass
|
||||
friendly_id
|
||||
gbifrb
|
||||
geocoder
|
||||
gibbon (~> 1.2.0)
|
||||
gravatar-ultimate
|
||||
@@ -685,8 +738,9 @@ DEPENDENCIES
|
||||
haml_lint (>= 0.25.1)
|
||||
hashie (>= 3.5.3)
|
||||
i18n-tasks
|
||||
icalendar
|
||||
jquery-rails
|
||||
jquery-ui-rails
|
||||
jquery-ui-rails!
|
||||
jsonapi-resources
|
||||
jsonapi-swagger
|
||||
leaflet-rails (>= 1.9.2)
|
||||
@@ -697,6 +751,7 @@ DEPENDENCIES
|
||||
material-sass (= 4.1.1)
|
||||
material_icons
|
||||
memcachier
|
||||
msgpack
|
||||
oj
|
||||
omniauth (~> 1.3)
|
||||
omniauth-flickr (>= 0.0.15)
|
||||
@@ -704,15 +759,17 @@ DEPENDENCIES
|
||||
percy-capybara (~> 5.0.0)
|
||||
pg
|
||||
platform-api
|
||||
pry
|
||||
puma
|
||||
query_diet
|
||||
rack-cors
|
||||
rack-protection (>= 2.0.1)
|
||||
rails (~> 7.0.7)
|
||||
rails (~> 7.1.0)
|
||||
rails-assets-leaflet.markercluster!
|
||||
rails-controller-testing
|
||||
rails_12factor
|
||||
rake (>= 10.0.0)
|
||||
recaptcha
|
||||
responders
|
||||
rspec-activemodel-mocks
|
||||
rspec-rails
|
||||
@@ -730,18 +787,18 @@ DEPENDENCIES
|
||||
selenium-webdriver
|
||||
sidekiq
|
||||
sprockets (< 4)
|
||||
terser
|
||||
timecop
|
||||
uglifier
|
||||
unicorn
|
||||
validate_url
|
||||
webdrivers
|
||||
vcr
|
||||
webrat
|
||||
will_paginate
|
||||
will_paginate-bootstrap-style
|
||||
xmlrpc
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.1.4p223
|
||||
ruby 3.1.4p223
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.11
|
||||
|
||||
@@ -5,7 +5,7 @@ jQuery ->
|
||||
$(".remove-altname-row").css("display", "inline-block")
|
||||
|
||||
-$ ->
|
||||
sci_template = "<div id='sci_template[INDEX]' class='template col-md-12'><div class='col-md-2'><label>Scientific name INDEX:</label></div><div class='col-md-8'><input name='sci_name[INDEX]' class='form-control', id='sci_name[INDEX]')'></input><span class='help-block'>Scientific name of crop.</span></div><div class='col-md-2'></div></div>"
|
||||
sci_template = "<div id='sci_template[INDEX]' class='template col-md-12'><div class='col-md-2'><label>Scientific name INDEX:</label></div><div class='col-md-8'><input name='sci_name[INDEX]' class='scientific-name-auto-suggest form-control' id='sci_name[INDEX]' data-source-url='/scientific_names/gbif_suggest')'></input><span class='help-block'>Scientific name of crop</span><input type='text' id='sci_gbif_key[INDEX]' class=''></div><div class='col-md-2'></div></div>"
|
||||
|
||||
sci_index = $('#scientific_names .template').length + 1
|
||||
|
||||
@@ -21,7 +21,7 @@ jQuery ->
|
||||
element = document.getElementById(tmp)
|
||||
element.remove()
|
||||
|
||||
alt_template = "<div id='alt_template[INDEX]' class='template col-md-12'><div class='col-md-2'><label>Alternate name INDEX:</label></div><div class='col-md-8'><input name='alt_name[INDEX]' class='form-control', id='alt_name[INDEX]')'></input><span class='help-block'>Alternate name of crop.</span></div><div class='col-md-2'></div></div>"
|
||||
alt_template = "<div id='alt_template[INDEX]' class='template col-md-12'><div class='col-md-2'><label>Alternate name INDEX:</label></div><div class='col-md-8'><input name='alt_name[INDEX]' class='form-control' id='alt_name[INDEX]')'></input><span class='help-block'>Alternate name of crop.</span></div><div class='col-md-2'></div></div>"
|
||||
|
||||
alt_index = $('#alternate_names .template').length + 1
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# TODO: This assumes one autocomplete per page.
|
||||
# Needs to be a function so that when we append one of these, it gets uniquely associated with the hidden controls.
|
||||
jQuery ->
|
||||
|
||||
if el = $( '.scientific-name-auto-suggest' )
|
||||
|
||||
id = $( '.scientific-name-auto-suggest-id' )
|
||||
|
||||
el.autocomplete
|
||||
minLength: 3,
|
||||
source: el.attr( 'data-source-url' ),
|
||||
focus: ( event, ui ) ->
|
||||
el.val( ui.item.canonicalName )
|
||||
id.val( ui.item.nameKey )
|
||||
false
|
||||
select: ( event, ui ) ->
|
||||
el.val( ui.item.canonicalName )
|
||||
id.val( ui.item.nameKey )
|
||||
false
|
||||
response: ( event, ui ) ->
|
||||
id.val( "" )
|
||||
for item in ui.content
|
||||
if item.name == el.val()
|
||||
id.val( item.nameKey )
|
||||
|
||||
if el.data( 'uiAutocomplete' )
|
||||
el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) ->
|
||||
$( '<li class="list-group-item"></li>' )
|
||||
.data( 'item.autocomplete', item )
|
||||
.append( "<a>#{item.canonicalName} (#{item.scientificName}) - #{item.rank}</a>" )
|
||||
.appendTo( ul )
|
||||
@@ -19,7 +19,7 @@
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
h5.crop-sci-name {
|
||||
.crop-sci-name {
|
||||
background-color: $beige;
|
||||
color: $black;
|
||||
font-size: 0.7em;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//$screen-md-min: 1028px
|
||||
|
||||
// Base colours
|
||||
$beige: #f3f1ee;
|
||||
$beige: #f4f2ef;
|
||||
$brown: #413f3b;
|
||||
|
||||
$green: #5f8e43;
|
||||
$green: #57803c;
|
||||
$blue: #2f4365;
|
||||
$red: #ff4d43;
|
||||
$orange: #ffa500;
|
||||
|
||||
@@ -101,12 +101,16 @@ section {
|
||||
background: $white;
|
||||
box-shadow: 1px 3px 3px 1px darken($beige, 20%);
|
||||
cursor: pointer;
|
||||
transition: 0.3s transform cubic-bezier(0.155, 1.105, 0.295, 1.12), 0.3s box-shadow,
|
||||
transition:
|
||||
0.3s transform cubic-bezier(0.155, 1.105, 0.295, 1.12),
|
||||
0.3s box-shadow,
|
||||
0.3s -webkit-transform cubic-bezier(0.155, 1.105, 0.295, 1.12);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 10px 20px darken($beige, 30%), 0 4px 8px darken($beige, 40%);
|
||||
box-shadow:
|
||||
0 10px 20px darken($beige, 30%),
|
||||
0 4px 8px darken($beige, 40%);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
@@ -354,7 +358,9 @@ ul.thumbnail-buttons {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: $blue;
|
||||
}
|
||||
.jumbotron {
|
||||
background-color: $beige;
|
||||
margin-bottom: 1em;
|
||||
@@ -364,15 +370,23 @@ ul.thumbnail-buttons {
|
||||
h1 {
|
||||
font-size: 400%;
|
||||
}
|
||||
.stats a {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
// signup widget on homepage
|
||||
.signup {
|
||||
background-color: lighten($green, 40%);
|
||||
border: 1px solid lighten($green, 20%);
|
||||
background-color: darken($green, 20%);
|
||||
border: 1px solid darken($green, 20%);
|
||||
color: $white;
|
||||
border-radius: 6px;
|
||||
line-height: 200%;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
|
||||
.btn-info {
|
||||
background-color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
|
||||
@@ -8,7 +8,7 @@ module Admin
|
||||
responders :flash
|
||||
|
||||
def index
|
||||
@roles = Role.all.order(:name)
|
||||
@roles = Role.all.order(:name).paginate(page: params[:page])
|
||||
respond_with @roles
|
||||
end
|
||||
|
||||
@@ -33,7 +33,7 @@ module Admin
|
||||
|
||||
def destroy
|
||||
@role.destroy
|
||||
respond_with @role, location: admin_roles_path
|
||||
respond_with @role, location: admin_roles_path, notice: "Role was successfully deleted"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -8,7 +8,7 @@ class AdminController < ApplicationController
|
||||
|
||||
def newsletter
|
||||
authorize! :manage, :all
|
||||
@members = Member.confirmed.wants_newsletter.all
|
||||
@members = Member.confirmed.wants_newsletter.all.paginate(page: params[:page], per_page: 100)
|
||||
respond_with @members
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ class AlternateNamesController < ApplicationController
|
||||
# GET /alternate_names
|
||||
# GET /alternate_names.json
|
||||
def index
|
||||
@alternate_names = AlternateName.all.order(:name)
|
||||
@alternate_names = AlternateName.all.order(:name).paginate(page: params[:page], per_page: 100)
|
||||
respond_with(@alternate_names)
|
||||
end
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ class CropsController < ApplicationController
|
||||
respond_with @crop, location: @crop
|
||||
end
|
||||
|
||||
def gbif
|
||||
@crop = Crop.find(params[:crop_slug])
|
||||
@crop.update_gbif_data!
|
||||
respond_with @crop, location: @crop
|
||||
end
|
||||
|
||||
def hierarchy
|
||||
@crops = Crop.toplevel.order(:name)
|
||||
respond_with @crops
|
||||
@@ -57,7 +63,7 @@ class CropsController < ApplicationController
|
||||
@crops = CropSearchService.search(@term,
|
||||
page: params[:page],
|
||||
per_page: Crop.per_page,
|
||||
current_member:)
|
||||
current_member:).to_a
|
||||
respond_with @crops
|
||||
end
|
||||
|
||||
@@ -120,6 +126,7 @@ class CropsController < ApplicationController
|
||||
if @crop.approval_status_changed?(from: "pending", to: "approved")
|
||||
notifier.deliver_now!
|
||||
@crop.update_openfarm_data!
|
||||
@crop.update_gbif_data!
|
||||
end
|
||||
else
|
||||
@crop.approval_status = @crop.approval_status_was
|
||||
@@ -163,7 +170,7 @@ class CropsController < ApplicationController
|
||||
return if params[param_name].blank?
|
||||
|
||||
destroy_names(name_type)
|
||||
params[param_name].each do |_i, value|
|
||||
params[param_name].each_value do |value|
|
||||
create_name!(name_type, value) unless value.empty?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
if resource.tos_agreement
|
||||
super resource
|
||||
super(resource)
|
||||
else
|
||||
finish_signup_path(resource)
|
||||
end
|
||||
|
||||
@@ -63,7 +63,7 @@ class PhotosController < ApplicationController
|
||||
|
||||
def photo_params
|
||||
params.require(:photo).permit(:source_id, :source, :title, :license_name,
|
||||
:license_url, :thumbnail_url, :fullsize_url, :link_url)
|
||||
:license_url, :thumbnail_url, :fullsize_url, :link_url, :date_taken)
|
||||
end
|
||||
|
||||
# Item with photos attached
|
||||
|
||||
@@ -6,7 +6,7 @@ class PlantPartsController < ApplicationController
|
||||
responders :flash
|
||||
|
||||
def index
|
||||
@plant_parts = PlantPart.all.order(:name)
|
||||
@plant_parts = PlantPart.all.order(:name).paginate(page: params[:page], per_page: 100)
|
||||
respond_with(@plant_parts)
|
||||
end
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
class PlantingsController < DataController
|
||||
after_action :update_crop_medians, only: %i(create update destroy)
|
||||
after_action :update_planting_medians, only: :update
|
||||
respond_to :ics, only: [:index] # TODO: This can be shifted up when all relevant controllers respond to ical
|
||||
|
||||
def index
|
||||
@show_all = params[:all] == '1'
|
||||
@@ -29,7 +30,6 @@ class PlantingsController < DataController
|
||||
)
|
||||
|
||||
@filename = "Growstuff-#{specifics}Plantings-#{Time.zone.now.to_fs(:number)}.csv"
|
||||
|
||||
respond_with(@plantings)
|
||||
end
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
class RegistrationsController < Devise::RegistrationsController
|
||||
respond_to :json
|
||||
|
||||
prepend_before_action :check_captcha, only: [:create] # Change this to be any actions you want to protect with recaptcha.
|
||||
|
||||
def edit
|
||||
@twitter_auth = current_member.auth('twitter')
|
||||
@flickr_auth = current_member.auth('flickr')
|
||||
@@ -46,6 +48,25 @@ class RegistrationsController < Devise::RegistrationsController
|
||||
render "edit"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sign_up_params
|
||||
params.require(:member).permit(:login_name, :email, :tos_agreement, :newsletter, :password, :password_confirmation)
|
||||
end
|
||||
|
||||
def check_captcha
|
||||
return if verify_recaptcha # verify_recaptcha(action: 'signup') for v3
|
||||
|
||||
self.resource = resource_class.new sign_up_params
|
||||
resource.validate # Look for any other validation errors besides reCAPTCHA
|
||||
set_minimum_password_length
|
||||
|
||||
respond_with_navigational(resource) do
|
||||
flash.discard(:recaptcha_error) # We need to discard flash to avoid showing it on the next page reload
|
||||
render :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# check if we need the current password to update fields
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ScientificNamesController < ApplicationController
|
||||
before_action :authenticate_member!, except: %i(index show)
|
||||
load_and_authorize_resource
|
||||
before_action :authenticate_member!, except: %i(index show gbif_suggest)
|
||||
load_and_authorize_resource except: [:gbif_suggest]
|
||||
respond_to :html, :json
|
||||
responders :flash
|
||||
|
||||
# GET /scientific_names
|
||||
# GET /scientific_names.json
|
||||
def index
|
||||
@scientific_names = ScientificName.all.order(:name)
|
||||
@scientific_names = ScientificName.all.order(:name).paginate(page: params[:page], per_page: 100)
|
||||
respond_with(@scientific_names)
|
||||
end
|
||||
|
||||
@@ -35,7 +35,7 @@ class ScientificNamesController < ApplicationController
|
||||
def create
|
||||
@scientific_name = ScientificName.new(scientific_name_params)
|
||||
@scientific_name.creator = current_member
|
||||
|
||||
gbif_sync!(@scientific_name)
|
||||
@scientific_name.save
|
||||
respond_with(@scientific_name.crop)
|
||||
end
|
||||
@@ -43,7 +43,9 @@ class ScientificNamesController < ApplicationController
|
||||
# PUT /scientific_names/1
|
||||
# PUT /scientific_names/1.json
|
||||
def update
|
||||
@scientific_name.update(scientific_name_params)
|
||||
@scientific_name.assign_attributes(scientific_name_params)
|
||||
gbif_sync!(@scientific_name)
|
||||
@scientific_name.save
|
||||
respond_with(@scientific_name.crop)
|
||||
end
|
||||
|
||||
@@ -56,9 +58,26 @@ class ScientificNamesController < ApplicationController
|
||||
respond_with(@crop)
|
||||
end
|
||||
|
||||
def gbif_suggest
|
||||
render json: gbif_service.suggest(params[:term])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gbif_sync!(model)
|
||||
return unless model.gbif_key
|
||||
|
||||
result = gbif_service.fetch(model.gbif_key)
|
||||
|
||||
model.gbif_rank = result["rank"]
|
||||
model.gbif_status = result["status"]
|
||||
end
|
||||
|
||||
def scientific_name_params
|
||||
params.require(:scientific_name).permit(:crop_id, :name)
|
||||
params.require(:scientific_name).permit(:crop_id, :name, :gbif_key)
|
||||
end
|
||||
|
||||
def gbif_service
|
||||
GbifService.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,7 +93,7 @@ module IconsHelper
|
||||
|
||||
def plant_part_icon(name)
|
||||
if File.exist? Rails.root.join('app', 'assets', 'images', 'icons', 'plant_parts', "#{name}.svg")
|
||||
image_tag "icons/plant_parts/#{name}.svg", class: 'img img-icon', 'aria-hidden' => "true"
|
||||
image_tag "icons/plant_parts/#{name}.svg", class: 'img img-icon', 'aria-hidden' => "true", alt: name
|
||||
else
|
||||
planting_icon
|
||||
end
|
||||
@@ -101,7 +101,7 @@ module IconsHelper
|
||||
|
||||
def crop_icon(crop)
|
||||
if crop.svg_icon.present?
|
||||
image_tag(crop_path(crop, format: 'svg'), class: 'crop-icon')
|
||||
image_tag(crop_path(crop, format: 'svg'), class: 'crop-icon', alt: crop)
|
||||
elsif crop.parent.present?
|
||||
crop_icon(crop.parent)
|
||||
else
|
||||
@@ -123,6 +123,6 @@ module IconsHelper
|
||||
end
|
||||
|
||||
def image_icon(icon)
|
||||
image_tag "icons/#{icon}.svg", class: 'img img-icon', 'aria-hidden' => "true"
|
||||
image_tag "icons/#{icon}.svg", class: 'img img-icon', 'aria-hidden' => "true", alt: icon
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,6 +79,7 @@ class Ability
|
||||
can :manage, ScientificName
|
||||
can :manage, AlternateName
|
||||
can :openfarm, Crop
|
||||
can :gbif, Crop
|
||||
end
|
||||
|
||||
# any member can create a crop provisionally
|
||||
|
||||
11
app/models/concerns/gbif_data.rb
Normal file
11
app/models/concerns/gbif_data.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module GbifData
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def update_gbif_data!
|
||||
GbifService.new.update_crop(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,7 @@ class Crop < ApplicationRecord
|
||||
extend FriendlyId
|
||||
include PhotoCapable
|
||||
include OpenFarmData
|
||||
include GbifData
|
||||
include SearchCrops
|
||||
|
||||
friendly_id :name, use: %i(slugged finders)
|
||||
|
||||
@@ -16,8 +16,13 @@ class Photo < ApplicationRecord
|
||||
Crop.distinct.joins(:photo_associations).where(photo_associations: { photo: self })
|
||||
end
|
||||
|
||||
validates :fullsize_url, url: true
|
||||
validates :thumbnail_url, url: true
|
||||
validates :fullsize_url, url: true, presence: true
|
||||
validates :thumbnail_url, url: true, presence: true
|
||||
validates :link_url, url: true, presence: true
|
||||
validates :owner, presence: true
|
||||
validates :title, presence: true
|
||||
validates :license_name, presence: true # Should assert this is one of CC-BY, CC-BY-NC, etc
|
||||
validates :license_url, url: true, allow_blank: true
|
||||
|
||||
# creates a relationship for each assignee type
|
||||
PHOTO_CAPABLE.each do |type|
|
||||
|
||||
@@ -24,8 +24,11 @@ class Post < ApplicationRecord
|
||||
#
|
||||
# Validations
|
||||
validates :subject, presence: true, length: { maximum: 255 }
|
||||
validates :body, presence: true
|
||||
|
||||
def author_date_subject
|
||||
return unless author
|
||||
|
||||
# slugs are created before created_at is set
|
||||
time = created_at || Time.zone.now
|
||||
"#{author.login_name} #{time.strftime('%Y%m%d')} #{subject}"
|
||||
|
||||
193
app/services/gbif_service.rb
Normal file
193
app/services/gbif_service.rb
Normal file
@@ -0,0 +1,193 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'English'
|
||||
class GbifService
|
||||
def initialize
|
||||
@cropbot = Member.find_by(login_name: 'cropbot')
|
||||
@species = Gbif::Species
|
||||
end
|
||||
|
||||
def suggest(term)
|
||||
# Query the GBIF name autocomplete and discover the scientific name.
|
||||
# [
|
||||
# {
|
||||
# "key": 2932942,
|
||||
# "nameKey": 1970347,
|
||||
# "kingdom": "Plantae",
|
||||
# "phylum": "Tracheophyta",
|
||||
# "order": "Solanales",
|
||||
# "family": "Solanaceae",
|
||||
# "genus": "Capsicum",
|
||||
# "species": "Capsicum chinense",
|
||||
# "kingdomKey": 6,
|
||||
# "phylumKey": 7707728,
|
||||
# "classKey": 220,
|
||||
# "orderKey": 1176,
|
||||
# "familyKey": 7717,
|
||||
# "genusKey": 2932937,
|
||||
# "speciesKey": 2932942,
|
||||
# "parent": "Capsicum",
|
||||
# "parentKey": 2932937,
|
||||
# "nubKey": 2932942,
|
||||
# "scientificName": "Capsicum chinense Jacq.",
|
||||
# "canonicalName": "Capsicum chinense",
|
||||
# "rank": "SPECIES",
|
||||
# "status": "ACCEPTED",
|
||||
# "synonym": false,
|
||||
# "higherClassificationMap": {
|
||||
# "6": "Plantae",
|
||||
# "220": "Magnoliopsida",
|
||||
# "1176": "Solanales",
|
||||
# "7717": "Solanaceae",
|
||||
# "2932937": "Capsicum",
|
||||
# "7707728": "Tracheophyta"
|
||||
# },
|
||||
# "class": "Magnoliopsida"
|
||||
# },
|
||||
# {
|
||||
# "key": 12079498,
|
||||
# "nameKey": 81778754,
|
||||
# "kingdom": "Plantae",
|
||||
# "phylum": "Tracheophyta",
|
||||
# "order": "Solanales",
|
||||
# "family": "Solanaceae",
|
||||
# "genus": "Capsicum",
|
||||
# "species": "Capsicum chinense",
|
||||
# "kingdomKey": 6,
|
||||
# "phylumKey": 7707728,
|
||||
# "classKey": 220,
|
||||
# "orderKey": 1176,
|
||||
# "familyKey": 7717,
|
||||
# "genusKey": 2932937,
|
||||
# "speciesKey": 2932942,
|
||||
# "parent": "Capsicum",
|
||||
# "parentKey": 2932937,
|
||||
# "nubKey": 12079498,
|
||||
# "scientificName": "Capsicum annuum var. chinense (Jacq.) Alef.",
|
||||
# "canonicalName": "Capsicum annuum chinense",
|
||||
# "rank": "VARIETY",
|
||||
# "status": "SYNONYM",
|
||||
# "synonym": true,
|
||||
# "higherClassificationMap": {
|
||||
# "6": "Plantae",
|
||||
# "220": "Magnoliopsida",
|
||||
# "1176": "Solanales",
|
||||
# "7717": "Solanaceae",
|
||||
# "2932937": "Capsicum",
|
||||
# "2932942": "Capsicum chinense",
|
||||
# "7707728": "Tracheophyta"
|
||||
# },
|
||||
# "class": "Magnoliopsida"
|
||||
# }
|
||||
# ]
|
||||
|
||||
@species.name_suggest(q: term)
|
||||
end
|
||||
|
||||
def import!
|
||||
Crop.order(updated_at: :desc).each do |crop|
|
||||
Rails.logger.debug { "#{crop.id}, #{crop.name}" }
|
||||
update_crop(crop) if crop.valid?
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
Rails.logger.error($ERROR_INFO.message)
|
||||
end
|
||||
end
|
||||
|
||||
def update_crop(crop)
|
||||
# Attempt to resolve the scientific names via /species/match.
|
||||
gbif_usage_key = crop.scientific_names.detect { |sn| sn.gbif_key.present? }&.gbif_key
|
||||
unless gbif_usage_key
|
||||
crop.scientific_names.each do |sn|
|
||||
result = @species.name_backbone(name: sn.name) # , higherTaxonKey: 6, nameType: 'SCIENTIFIC')
|
||||
next unless result["confidence"] > 95 && result["matchType"] == "EXACT"
|
||||
|
||||
sn.gbif_key = result["usageKey"]
|
||||
sn.gbif_rank = result["rank"]
|
||||
sn.gbif_status = result["status"]
|
||||
sn.save!
|
||||
end
|
||||
|
||||
gbif_usage_key = crop.scientific_names.detect { |sn| sn.gbif_key.present? }&.gbif_key
|
||||
end
|
||||
|
||||
# No match? Fall back to common names
|
||||
unless gbif_usage_key
|
||||
query_results = @species.name_lookup(q: crop.name, higherTaxonKey: 6)
|
||||
|
||||
# We only want one result, otherwise it needs human.
|
||||
return unless query_results["results"].length == 1
|
||||
|
||||
query_result = query_results["results"].first
|
||||
|
||||
gbif_usage_key = query_result["key"]
|
||||
|
||||
crop.scientific_names.create!(gbif_key: gbif_usage_key, name: query_result["canonicalName"], creator: @cropbot)
|
||||
end
|
||||
|
||||
gbif_record = fetch(gbif_usage_key)
|
||||
if gbif_record.present?
|
||||
# crop.update! openfarm_data: gbif_record.fetch('data', false)
|
||||
# save_companions(crop, gbif_record)
|
||||
save_photos(crop, gbif_usage_key)
|
||||
else
|
||||
Rails.logger.debug "\tcrop not found on GBIF"
|
||||
# crop.update!(openfarm_data: false)
|
||||
end
|
||||
end
|
||||
|
||||
def save_photos(crop, key)
|
||||
# https://api.gbif.org/v1/occurrence/search?taxon_key=3084850
|
||||
|
||||
occurrences = Gbif::Occurrences.search(taxonKey: key, mediatype: 'StillImage', limit: 3, hasCoordinate: true)
|
||||
occurrences["results"].each do |result|
|
||||
next unless result["media"]
|
||||
|
||||
media = result["media"].first
|
||||
next unless media["identifier"]
|
||||
|
||||
# Example: "https://inaturalist-open-data.s3.amazonaws.com/photos/250226497/original.jpg"
|
||||
url = media["identifier"]
|
||||
md5 = Digest::MD5.hexdigest(url)
|
||||
width = 200
|
||||
thumbnail = "https://api.gbif.org/v1/image/cache/#{width}x/occurrence/#{result['key']}/media/#{md5}"
|
||||
|
||||
next unless url.start_with? 'http'
|
||||
next if Photo.find_by(source_id: result["key"], source: 'gbif')
|
||||
next if media["references"].blank?
|
||||
|
||||
photo = Photo.new(
|
||||
# This is for the overall observation which may technically have multiple media. However, we're only taking the first.
|
||||
source_id: result["key"],
|
||||
source: 'gbif',
|
||||
owner: @cropbot,
|
||||
thumbnail_url: thumbnail,
|
||||
fullsize_url: url,
|
||||
title: "Photo by #{media['creator']} via #{media['publisher']} (Copyright #{media['rightsHolder']})",
|
||||
license_name: case media["license"]
|
||||
when "http://creativecommons.org/licenses/by/4.0/"
|
||||
"CC BY 4.0"
|
||||
when "http://creativecommons.org/licenses/by-nc/4.0/"
|
||||
"CC BY-NC 4.0"
|
||||
else
|
||||
media["license"]
|
||||
end,
|
||||
license_url: media["license"],
|
||||
link_url: media["references"]
|
||||
)
|
||||
photo.date_taken = DateTime.parse(media["created"]) if media["created"]
|
||||
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(key)
|
||||
Gbif::Request.new("species/#{key}", nil, nil, nil).perform
|
||||
end
|
||||
end
|
||||
@@ -30,6 +30,7 @@
|
||||
%li= link_to "Crop Wrangling", wrangle_crops_path, class: 'nav-link'
|
||||
%li= link_to "Alternate names", alternate_names_path, class: 'nav-link'
|
||||
%li= link_to "Scientific names", scientific_names_path, class: 'nav-link'
|
||||
%li= link_to "Plant parts", plant_parts_path, class: 'nav-link'
|
||||
|
||||
.col-md-4
|
||||
.card
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
- @members.each do |m|
|
||||
= m.email
|
||||
%br/
|
||||
= will_paginate @members
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%li.breadcrumb-item.active= link_to 'Roles', admin_roles_path
|
||||
|
||||
- if can? :create, Role
|
||||
%p= link_to 'New Role', new_admin_role_path, class: 'btn btn-primary'
|
||||
%p= link_to 'New role', new_admin_role_path, class: 'btn btn-primary'
|
||||
|
||||
%table.table.table-striped
|
||||
%thead
|
||||
@@ -25,6 +25,8 @@
|
||||
= edit_icon
|
||||
= t('.edit')
|
||||
- if can?(:destroy, role) && ! role.members.any?
|
||||
= link_to admin_role_path(role), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs text-danger' do
|
||||
= link_to admin_role_path(role), method: :delete, data: { confirm: t(:are_you_sure?) }, class: 'btn btn-default btn-xs text-danger' do
|
||||
= delete_icon
|
||||
= t('.delete')
|
||||
|
||||
= will_paginate(@roles)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
- if content_for? :title
|
||||
%h1.h2-responsive.text-center
|
||||
%strong=yield :title
|
||||
= form_for @alternate_name, html: { class: 'form-horizontal', role: "form" } do |f|
|
||||
= form_for @alternate_name, html: { class: 'form-horizontal' } do |f|
|
||||
- if @alternate_name.errors.any?
|
||||
#error_explanation
|
||||
%h2
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
%td= link_to 'Show', alternate_name
|
||||
%td
|
||||
- if can? :edit, alternate_name
|
||||
= link_to 'Edit', edit_alternate_name_path(alternate_name), class: 'btn btn-default btn-xs'
|
||||
= link_to t('buttons.edit'), edit_alternate_name_path(alternate_name), class: 'btn btn-default btn-xs'
|
||||
%td
|
||||
- if can? :destroy, alternate_name
|
||||
= link_to 'Delete', alternate_name,
|
||||
method: :delete,
|
||||
data: { confirm: 'Are you sure?' },
|
||||
class: 'btn btn-default btn-xs'
|
||||
= link_to t('buttons.delete'), alternate_name,
|
||||
method: :delete,
|
||||
data: { confirm: t(:are_you_sure?) },
|
||||
class: 'btn btn-default btn-xs'
|
||||
= will_paginate(@alternate_names)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
- if content_for? :title
|
||||
%h1.h2-responsive.text-center
|
||||
%strong=yield :title
|
||||
= form_for(@comment, html: { class: "form-horizontal", role: "form" }) do |f|
|
||||
= form_for(@comment, html: { class: "form-horizontal" }) do |f|
|
||||
- if @comment.errors.any?
|
||||
#error_explanation
|
||||
%h2
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
%item
|
||||
%title Comment by #{comment.author.login_name} on #{comment.post.subject}
|
||||
%description
|
||||
:escaped
|
||||
:escaped_markdown
|
||||
<p>
|
||||
Comment on
|
||||
#{ link_to comment.post.subject, post_url(comment.post) }
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
= truncate(strip_tags(conversation.messages.last.body), length: 150, separator: ' ', omission: '... ')
|
||||
.col-md-1
|
||||
- if @box == 'trash'
|
||||
= link_to conversation_path(conversation, box: @box), method: :put, class: 'restore' do
|
||||
= link_to conversation_path(conversation, box: @box), method: :put, class: 'restore', title: "Restore" do
|
||||
= icon 'fas', 'trash-restore'
|
||||
- else
|
||||
= check_box_tag 'conversation_ids[]', conversation.id, false, class: 'selectable'
|
||||
= check_box_tag 'conversation_ids[]', conversation.id, false, class: 'selectable', "aria-label": "Select for deletion"
|
||||
- unless @conversations.empty?
|
||||
= will_paginate @conversations
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.card.card-crop
|
||||
.crop-image
|
||||
= link_to image_tag(crop_image_path(crop),
|
||||
alt: '',
|
||||
alt: "Image of #{crop.name}",
|
||||
class: 'img img-card'),
|
||||
crop
|
||||
.card-body
|
||||
|
||||
@@ -65,8 +65,12 @@
|
||||
.col-2
|
||||
= label_tag :scientific_names, "Scientific name #{index + 1}:", class: 'control-label'
|
||||
.col-8
|
||||
= text_field_tag "sci_name[#{index + 1}]", sci.name, id: "sci_name[#{index + 1}]", class: 'form-control'
|
||||
%span.help-block Scientific name of crop.
|
||||
= text_field_tag "sci_name[#{index + 1}]", sci.name, id: "sci_name[#{index + 1}]",
|
||||
class: 'scientific-name-auto-suggest form-control',
|
||||
data: { source_url: gbif_suggest_scientific_names_path }
|
||||
%span.help-block Searches GBIF to determine scientific name of crop.
|
||||
= hidden_field_tag "sci_gbif_key[#{index + 1}]", sci.gbif_key, id: "sci_gbif_key[#{index + 1}]",
|
||||
class: 'scientific-name-auto-suggest-id'
|
||||
%h2 Alternate names
|
||||
= button_tag "+", class: "add-altname-row", type: "button"
|
||||
= button_tag "-", class: "remove-altname-row", type: "button"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- cache crop do
|
||||
= link_to image_tag(crop_image_path(crop),
|
||||
alt: crop.name, class: 'image-responsive crop-image'),
|
||||
alt: "Image of #{crop.name}", class: 'image-responsive crop-image'),
|
||||
crop.name,
|
||||
rel: "popover",
|
||||
'data-trigger': 'hover',
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
%p= simple_format @crop.description
|
||||
.col-md-3
|
||||
= image_tag crop_image_path(@crop),
|
||||
class: 'img-responsive shadow rounded crop-hero-photo', alt: 'photo of crop'
|
||||
class: 'img-responsive shadow rounded crop-hero-photo', alt: "Image of #{@crop.name}"
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
= delete_icon
|
||||
= t('.delete')
|
||||
- else
|
||||
.badge= sn.name
|
||||
- if sn.gbif_key
|
||||
= link_to sn.name, "https://www.gbif.org/species/#{sn.gbif_key}",
|
||||
class: 'card-link',
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
- else
|
||||
.badge= sn.name
|
||||
|
||||
%p.text-right
|
||||
- if can? :edit, crop
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
.card.crop-thumbnail
|
||||
= link_to crop_path(slug: crop.slug) do
|
||||
= image_tag(crop.thumbnail_url.presence || placeholder_image,
|
||||
alt: crop.name,
|
||||
alt: "Image of #{crop.name}",
|
||||
class: 'img img-card')
|
||||
|
||||
|
||||
.text
|
||||
%h3.crop-name= link_to crop.name, crop_path(slug: crop.slug)
|
||||
%h5.crop-sci-name
|
||||
= crop.scientific_names.first
|
||||
- if crop.scientific_names.any?
|
||||
%div.crop-sci-name
|
||||
= crop.scientific_names.first
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
= icon 'far', 'update'
|
||||
Fetch data from OpenFarm
|
||||
|
||||
= link_to crop_gbif_path(crop), method: :post, class: 'dropdown-item' do
|
||||
= icon 'far', 'update'
|
||||
Fetch data from GBIF
|
||||
|
||||
- if can? :destroy, crop
|
||||
.dropdown-divider
|
||||
= delete_button(crop, classes: 'dropdown-item text-danger')
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
= link_to crop_plantings_path(@crop), class: 'card-link' do
|
||||
= planting_icon
|
||||
#{@crop.name.capitalize} plantings
|
||||
%span.badge.badge-primary.badge-pill=@crop.plantings.size
|
||||
%span.badge.badge-primary.badge-pill=@crop.plantings.active.size
|
||||
.list-group-item.d-flex.justify-content-between.align-items-center
|
||||
= link_to crop_harvests_path(@crop), class: 'card-link' do
|
||||
= harvest_icon
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
%h1 Join #{ENV['GROWSTUFF_SITE_NAME']}
|
||||
.card-body
|
||||
%p Sign up for a #{ENV['GROWSTUFF_SITE_NAME']} account to track your vegetable garden and connect with other local growers.
|
||||
%p If you have accessibility issues with the captcha, please contact us via the links in the footer and we will help.
|
||||
|
||||
= bootstrap_form_for(resource, as: resource_name, url: registration_path(resource_name),
|
||||
html: { class: "text-center border border-light p-5" }) do |f|
|
||||
html: { class: "text-center border border-light p-5", data: { turbo: false } }) do |f|
|
||||
= render 'devise/shared/error_messages', resource: resource
|
||||
|
||||
= f.text_field :login_name
|
||||
@@ -28,4 +29,9 @@
|
||||
|
||||
= f.submit "Sign up", class: 'btn btn-block btn-success'
|
||||
|
||||
-# START add reCAPTCHA
|
||||
= flash[:recaptcha_error]
|
||||
= recaptcha_tags
|
||||
-# END add reCAPTCHA
|
||||
|
||||
.card-footer= render "devise/shared/links"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= form_for @garden_type, html: { class: 'form-horizontal', role: "form" } do |f|
|
||||
= form_for @garden_type, html: { class: 'form-horizontal' } do |f|
|
||||
- if @garden_type.errors.any?
|
||||
#error_explanation
|
||||
%h2= "#{pluralize(@garden_type.errors.count, "error")} prohibited this garden_type from being saved:"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card
|
||||
= link_to garden do
|
||||
= image_tag garden_image_path(garden), class: 'img-card', alt: garden
|
||||
= image_tag garden_image_path(garden), class: 'img-card', alt: "Image of #{garden.name}"
|
||||
.card-body.text-center
|
||||
%h4.card-title= garden.name
|
||||
@@ -1,3 +1,3 @@
|
||||
= link_to image_tag(garden_image_path(garden),
|
||||
alt: garden.name, class: 'img-responsive'),
|
||||
alt: "Image of #{garden.name}", class: 'img-responsive'),
|
||||
garden_path(garden)
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
.row
|
||||
.col-md-2
|
||||
= render 'layouts/nav', model: Garden
|
||||
= link_to show_inactive_tickbox_path('gardens', owner: @owner, show_all: @show_all) do
|
||||
= check_box_tag 'active', 'all', @show_all
|
||||
include in-active
|
||||
%label
|
||||
= link_to show_inactive_tickbox_path('gardens', owner: @owner, show_all: @show_all) do
|
||||
= check_box_tag 'active', 'all', @show_all
|
||||
include in-active
|
||||
- if @owner.present?
|
||||
%hr/
|
||||
= render @owner
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
%h3
|
||||
Harvested
|
||||
= editable :date, @harvest, :harvested_at, display_field: '.harvested_at'
|
||||
%strong.harvested_at #{time_ago_in_words @harvest.harvested_at} ago
|
||||
%strong.harvested_at #{distance_of_time_in_words @harvest.harvested_at, Time.zone.now.to_date} ago
|
||||
%span.harvested_at= I18n.l @harvest.harvested_at
|
||||
|
||||
.card{class: @harvest.quantity.present? ? '' : 'text-muted'}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
.row.homepage-blurb
|
||||
.col-md-8.info
|
||||
%h1 Growstuff - An open gardening platform
|
||||
%p= t('.intro', site_name: ENV['GROWSTUFF_SITE_NAME'])
|
||||
= render 'stats'
|
||||
.col-md-4
|
||||
.signup
|
||||
%p= t('.perks')
|
||||
%p= link_to(t('.sign_up'), new_member_registration_path, class: 'btn btn-info btn-block')
|
||||
%p= link_to(t('.sign_up'), new_member_registration_path, class: 'btn btn-primary btn-block')
|
||||
%p= t('.already_html', sign_in: link_to(t('.sign_in_linktext'), new_member_session_path, class: 'btn btn-primary'))
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
= link_to harvest_path(slug: harvest.slug), class: 'list-group-item list-group-item-action flex-column align-items-start' do
|
||||
.d-flex.w-100.justify-content-between.homepage--list-item
|
||||
%div
|
||||
%h5= harvest.crop_name
|
||||
%h4= harvest.crop_name
|
||||
%span.badge.badge-success=harvest.plant_part
|
||||
%small.text-muted
|
||||
harvested by #{harvest.owner_login_name}
|
||||
%p.mb-2
|
||||
= image_tag harvest.thumbnail_url, width: 75, class: 'rounded shadow'
|
||||
= image_tag harvest.thumbnail_url, width: 75, class: 'rounded shadow', alt: "Image of #{harvest.crop_name} by #{harvest.owner}"
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
= link_to planting_path(slug: planting['slug']), class: 'list-group-item list-group-item-action flex-column align-items-start' do
|
||||
.d-flex.w-100.justify-content-between.homepage--list-item
|
||||
%p.mb-2
|
||||
= image_tag planting['thumbnail_url'], width: 75, class: 'rounded shadow'
|
||||
= image_tag planting['thumbnail_url'], width: 75, class: 'rounded shadow', alt: "Image of #{planting['crop_name']} by #{planting['owner_login_name']}"
|
||||
.text-right
|
||||
%h5= planting['crop_name']
|
||||
%h4= planting['crop_name']
|
||||
- if planting['planted_from'].present?
|
||||
%span.badge.badge-success= planting['planted_from'].pluralize
|
||||
%small.text-muted planted by #{planting['owner_login_name']}
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
= render 'crops'
|
||||
= link_to "#{t('home.crops.view_all')} »", crops_path, class: 'btn btn-block'
|
||||
.col-xl-3.col-12
|
||||
%section.recent-crops
|
||||
%section.recent-crops.card
|
||||
- cache cache_key_for(Crop, 'recent') do
|
||||
%h2= t('.recently_added')
|
||||
%p
|
||||
%p.card-body
|
||||
!= CropSearchService.recent(30).map { |c| link_to(c['name'], crop_path(slug: c['slug'])) }.join(", ")
|
||||
.col-xl-3.col
|
||||
%section.plantings
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
- if signed_in?
|
||||
%li.nav-item
|
||||
= link_to timeline_index_path, method: :get, class: 'nav-link text-white' do
|
||||
= image_tag 'icons/notification.svg', class: 'img img-icon'
|
||||
= image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications"
|
||||
%li.nav-item
|
||||
= link_to member_gardens_path(current_member), class: 'nav-link text-white' do
|
||||
= link_to member_gardens_path(current_member), class: 'nav-link text-white', title: "My gardens" do
|
||||
= image_icon 'gardens'
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"}
|
||||
= image_tag "icons/gardener.svg", class: 'img img-icon'
|
||||
= image_tag "icons/gardener.svg", class: 'img img-icon', alt: t('.record'), aria: { hidden: "true" }
|
||||
= t('.record')
|
||||
.dropdown-menu
|
||||
= link_to new_planting_path, class: 'dropdown-item' do
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"}
|
||||
= image_tag(avatar_uri(current_member, 50), alt: '', height: 25, width: 25)
|
||||
= image_tag(avatar_uri(current_member, 50), alt: 'Avatar of current member', height: 25, width: 25, aria: { hidden: "true" })
|
||||
= current_member.login_name
|
||||
- if current_member.unread_count.positive?
|
||||
%span.badge.badge-info= current_member.unread_count
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
- if current_member.present?
|
||||
.flex-column.nav-pills.layout-nav
|
||||
.flex-column.nav-pills.layout-nav{"role" => "tablist", "aria-orientation"=>"vertical"}
|
||||
/ %h2.card-title #{model} links
|
||||
= link_to url_for([current_member, model]), class: 'nav-link' do
|
||||
= link_to url_for([current_member, model]), class: 'nav-link tab' do
|
||||
My #{model.model_name.human.pluralize}
|
||||
= link_to model, class: 'nav-link' do
|
||||
= link_to model, class: 'nav-link tab' do
|
||||
Everyone's #{model.model_name.human.pluralize}
|
||||
- if can?(:create, model)
|
||||
= link_to url_for([model, action: :new]), class: 'btn' do
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
.container
|
||||
#maincontainer
|
||||
- if content_for?(:breadcrumbs)
|
||||
%ol.breadcrumb{ "aria-label" => "breadcrumb" }
|
||||
%li.breadcrumb-item= link_to 'Home', root_path
|
||||
= yield(:breadcrumbs)
|
||||
%nav{ "aria-label" => "breadcrumb" }
|
||||
%ol.breadcrumb
|
||||
%li.breadcrumb-item= link_to 'Home', root_path
|
||||
= yield(:breadcrumbs)
|
||||
- if content_for?(:buttonbar)
|
||||
= yield(:buttonbar)
|
||||
|
||||
@@ -22,7 +23,7 @@
|
||||
%small= yield(:subtitle)
|
||||
|
||||
= render "shared/flash_messages", flash: flash
|
||||
= yield
|
||||
%main= yield
|
||||
|
||||
%footer.page-footer.font-small.bg-dark.pt-4= render "layouts/footer"
|
||||
/
|
||||
|
||||
@@ -1 +1 @@
|
||||
= link_to image_tag(avatar_uri(member, 150), alt: member, class: 'avatar img img-fluid'), member_path(member)
|
||||
= link_to image_tag(avatar_uri(member, 150), alt: "Avatar of #{member}", class: 'avatar img img-fluid'), member_path(member)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
= member
|
||||
- else
|
||||
= link_to member do
|
||||
= image_tag(avatar_uri(member, 100), alt: '', height: 50, width: 50)
|
||||
= image_tag(avatar_uri(member, 100), alt: member.login_name, height: 50, width: 50)
|
||||
= member
|
||||
|
||||
|
||||
|
||||
@@ -74,6 +74,9 @@
|
||||
= render 'timeline/photos', photo: resolve_model(event) if event.event_type == 'photo'
|
||||
%small
|
||||
- if event.event_at.present?
|
||||
#{time_ago_in_words(event.event_at)} ago
|
||||
- if event.event_at.kind_of?(Date)
|
||||
#{distance_of_time_in_words(event.event_at, Time.zone.now.to_date)} ago
|
||||
- else
|
||||
#{time_ago_in_words(event.event_at)} ago
|
||||
- else
|
||||
unknown date
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
%h5.ellipsis
|
||||
= photo_icon
|
||||
= link_to photo.title, photo_path(id: photo.id)
|
||||
%i by #{link_to photo.owner_login_name, member_path(slug: photo.owner_slug)}
|
||||
- if photo.owner_slug
|
||||
%i by #{link_to photo.owner_login_name, member_path(slug: photo.owner_slug)}
|
||||
- if photo.date_taken.present?
|
||||
%small.text-muted
|
||||
%time{datetime: photo.date_taken}= I18n.l(photo.date_taken.to_date)
|
||||
|
||||
@@ -5,6 +5,31 @@
|
||||
= form_for(@photo) do |f|
|
||||
.form-group
|
||||
= f.label :title
|
||||
= f.text_field :title, placeholder: "title"
|
||||
= f.text_field :title, placeholder: "title", required: true
|
||||
|
||||
.form-group
|
||||
= f.label :thumbnail_url
|
||||
= f.url_field :thumbnail_url
|
||||
|
||||
.form-group
|
||||
= f.label :fullsize_url
|
||||
= f.url_field :fullsize_url
|
||||
|
||||
.form-group
|
||||
= f.label :link_url
|
||||
= f.url_field :link_url
|
||||
|
||||
.form-group
|
||||
= f.label :license_name
|
||||
= f.text_field :license_name
|
||||
|
||||
.form-group
|
||||
= f.label :license_url
|
||||
= f.text_field :license_url
|
||||
|
||||
.form-group
|
||||
= f.label :date_taken
|
||||
= f.datetime_field :date_taken
|
||||
|
||||
.form-group
|
||||
.form-actions= f.submit 'Save', class: 'btn'
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item= link_to 'Photos', photos_path
|
||||
|
||||
= page_entries_info @photos
|
||||
= will_paginate @photos
|
||||
|
||||
.index-cards
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- content_for :title, "New Photo"
|
||||
|
||||
%h1 New Photo
|
||||
%h2 Choose photo for #{link_to @item, @item}
|
||||
%h2 Choose photo for #{link_to @item, @item} from Flickr, or contribute to unique crops to <a href="https://inaturalist.org/" target="_blank">iNaturalist</a> or <a href="https://identify.plantnet.org/" target="_blank">Pl@ntNet</a> via the app.
|
||||
|
||||
- if @please_reconnect_flickr
|
||||
%h2.alert Please reconnect your flickr account
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%h1 Plant Parts
|
||||
|
||||
- if can? :create, PlantPart
|
||||
= link_to 'New Plant part', new_plant_part_path, class: 'btn btn-info'
|
||||
= link_to 'New plant part', new_plant_part_path, class: 'btn btn-info'
|
||||
|
||||
.index-cards
|
||||
- @plant_parts.each do |plant_part|
|
||||
@@ -20,6 +20,8 @@
|
||||
.card-footer
|
||||
%p
|
||||
- if can? :edit, plant_part
|
||||
= link_to 'Edit', edit_plant_part_path(plant_part), class: 'btn btn-default btn-xs'
|
||||
= link_to t('buttons.edit'), edit_plant_part_path(plant_part), class: 'btn btn-default btn-xs'
|
||||
- if can? :destroy, plant_part
|
||||
= link_to 'Delete', plant_part, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs'
|
||||
= link_to t('buttons.delete'), plant_part, method: :delete, data: { confirm: t(:are_you_sure?) }, class: 'btn btn-default btn-xs'
|
||||
|
||||
= will_paginate(@plant_parts)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
%h1 #{@plant_part.name.titlecase}
|
||||
- if @plant_part.crops.empty?
|
||||
%p No crops are harvested for this plant part (yet).
|
||||
- else
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
= link_to plantings_path(planting: {crop_id: planting.crop_id, garden_id: garden.id}), method: :post do
|
||||
.md-v-line
|
||||
.d-flex.justify-content-between
|
||||
= image_tag garden_image_path(garden), class: 'img', height: 50
|
||||
= image_tag garden_image_path(garden), class: 'img', height: 50, alt: garden.name
|
||||
%span
|
||||
%h4= garden.name
|
||||
%p= garden.description
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
- else
|
||||
%p No photos.
|
||||
- if can?(:edit, planting) && can?(:create, Photo)
|
||||
%p Add a photo to visually track growth of this planting
|
||||
%p Add a photo to visually track growth of this planting, to Flickr, iNaturalist or Pl@ntNet
|
||||
= add_photo_button(planting)
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
= link_to (@owner ? member_plantings_path(@owner, format: format) : plantings_path(format: format)) do
|
||||
= icon 'fas', format.to_s
|
||||
= format.upcase
|
||||
- if @owner
|
||||
.badge.badge-info= link_to "iCal", member_plantings_path(@owner, format: 'ics', protocol: 'webcal', only_path: false)
|
||||
.badge.badge-success= link_to 'API Methods', '/api-docs'
|
||||
|
||||
.col-md-10
|
||||
|
||||
42
app/views/plantings/index.ics.erb
Normal file
42
app/views/plantings/index.ics.erb
Normal file
@@ -0,0 +1,42 @@
|
||||
<%
|
||||
# TODO Refactor to a Planting <-> Ical view class?
|
||||
cal = Icalendar::Calendar.new
|
||||
cal.description = "Plantings by #{@owner.login_name}"
|
||||
@plantings.each do |planting|
|
||||
|
||||
event = Icalendar::Event.new
|
||||
|
||||
lines = []
|
||||
lines << "Quantity: #{planting['quantity'] ? planting['quantity'] : 'unknown' }"
|
||||
lines << "Planted on: #{planting['planted_at'] ? planting['planted_at'] : 'unknown' }"
|
||||
lines << "Sunniness: #{planting['sunniness'] ? planting['sunniness'] : 'unknown' }"
|
||||
lines << "Planted from: #{planting['planted_from'] ? planting['planted_from'] : 'unknown' }"
|
||||
lines << "First harvest from: #{planting['first_harvest_predicted_at'] ? planting['first_harvest_predicted_at'] : 'unknown' }"
|
||||
lines << "Last harvest from: #{planting['last_harvest_predicted_at'] ? planting['last_harvest_predicted_at'] : 'unknown' }"
|
||||
lines << "Finish predicted at: #{planting['finish_predicted_at'] ? planting['finish_predicted_at'] : 'unknown'}"
|
||||
lines << "Finished at: #{planting['finished_at'] ? planting['finished_at'] : 'unknown' }"
|
||||
|
||||
lines << planting.description
|
||||
finish_date = Date.parse(planting['finished_at'] || planting['finish_predicted_at'] || planting['last_harvest_predicted_at']) rescue nil
|
||||
|
||||
event.dtstart = Time.at(planting['created_at'])
|
||||
event.dtend = finish_date || 1.day.from_now
|
||||
event.summary = planting['crop_name']
|
||||
event.description = lines.join("\n")
|
||||
event.ip_class = "PUBLIC"
|
||||
event.url = planting_url(slug: planting['slug'])
|
||||
|
||||
cal.add_event(event)
|
||||
|
||||
if finish_date && finish_date > Date.today
|
||||
todo = Icalendar::Todo.new
|
||||
todo.dtstart = planting['first_harvest_predicted_at'] || finish_date || Date.today
|
||||
todo.due = finish_date
|
||||
todo.summary = "Harvest #{planting['crop_name']}"
|
||||
|
||||
cal.add_todo(todo)
|
||||
end
|
||||
end
|
||||
cal.publish
|
||||
%>
|
||||
<%= cal.to_ical %>
|
||||
@@ -76,7 +76,7 @@
|
||||
= link_to planting, class: 'list-group-item list-group-item-action flex-column align-items-start' do
|
||||
.d-flex.w-100.justify-content-between
|
||||
%p.mb-2
|
||||
= image_tag planting_image_path(planting), width: 75, class: 'rounded shadow'
|
||||
= image_tag planting_image_path(planting), width: 75, class: 'rounded shadow', alt: "Image of #{planting.crop.name} by #{planting.owner}"
|
||||
.text-right
|
||||
%h5= planting.crop.name
|
||||
- if planting.planted_from.present?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
.view
|
||||
= link_to post do
|
||||
= image_tag post_image_path(post), class: 'img img-cover'
|
||||
= image_tag post_image_path(post), class: 'img img-cover', alt: "A photo related to this post"
|
||||
%h4.font-weight-bold.mb-3
|
||||
%strong
|
||||
= link_to post do
|
||||
@@ -16,5 +16,5 @@
|
||||
= link_to crop do
|
||||
= crop_icon(crop)
|
||||
= crop.name.pluralize
|
||||
/ = image_tag avatar_uri(post.author, 50), class: 'avatar'
|
||||
/ = image_tag avatar_uri(post.author, 50), class: 'avatar', alt: post.author
|
||||
= link_to 'Read more', post, class: 'btn btn-rounded btn-md'
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
%h1= @author ? t('.title.author_posts', author: @author) : t('.title.default')
|
||||
|
||||
.row
|
||||
.col-2
|
||||
.col-8= will_paginate @posts
|
||||
.col-md-2
|
||||
.col-md-8= will_paginate @posts
|
||||
.row
|
||||
.col-2
|
||||
.col-md-2
|
||||
= render 'layouts/nav', model: Post
|
||||
%hr/
|
||||
%p
|
||||
@@ -28,7 +28,7 @@
|
||||
or
|
||||
= succeed "." do
|
||||
= link_to "comments RSS feed", comments_path(format: 'rss')
|
||||
.col-10
|
||||
.col-md-10
|
||||
.row.posts
|
||||
- @posts.each do |post|
|
||||
.col-lg-3.col-md-6.mb-3.post
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
%title Comment by #{comment.author.login_name} on #{comment.created_at}
|
||||
%description
|
||||
|
||||
:escaped
|
||||
:escaped_markdown
|
||||
<p>
|
||||
Comment on
|
||||
#{ link_to @post.subject, post_url(@post) }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= form_for @scientific_name, html: { class: 'form-horizontal', role: "form" } do |f|
|
||||
= form_for @scientific_name, html: { class: 'form-horizontal' } do |f|
|
||||
- if @scientific_name.errors.any?
|
||||
#error_explanation
|
||||
%h2
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
%td= link_to 'Show', scientific_name
|
||||
%td
|
||||
- if can? :edit, scientific_name
|
||||
= link_to 'Edit', edit_scientific_name_path(scientific_name), class: 'btn btn-default btn-xs'
|
||||
= link_to t('buttons.edit'), edit_scientific_name_path(scientific_name), class: 'btn btn-default btn-xs'
|
||||
%td
|
||||
- if can? :destroy, scientific_name
|
||||
= link_to 'Delete', scientific_name, method: :delete, data: { confirm: 'Are you sure?' },
|
||||
= link_to t('buttons.delete'), scientific_name, method: :delete, data: { confirm: t(:are_you_sure?) },
|
||||
class: 'btn btn-default btn-xs'
|
||||
|
||||
= will_paginate @scientific_names
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.media
|
||||
= link_to(image_tag(photo.thumbnail_url, width: 150, class: 'rounded'), photo)
|
||||
= link_to(image_tag(photo.thumbnail_url, width: 150, class: 'rounded', alt: photo.title), photo)
|
||||
.media-body
|
||||
%p
|
||||
%ul.associations
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
= render 'timeline/photos', photo: resolve_model(event) if event.event_type == 'photo'
|
||||
%small
|
||||
- if event.event_at.present?
|
||||
#{time_ago_in_words(event.event_at)} ago
|
||||
- if event.event_at.kind_of?(Date)
|
||||
#{distance_of_time_in_words(event.event_at, Time.zone.now.to_date)} ago
|
||||
- else
|
||||
#{time_ago_in_words(event.event_at)} ago
|
||||
- else
|
||||
unknown date
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
require_relative 'boot'
|
||||
|
||||
require 'rails/all'
|
||||
ENV['RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION'] = "true"
|
||||
|
||||
require 'openssl'
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
@@ -16,8 +18,6 @@ module Growstuff
|
||||
|
||||
I18n.config.enforce_available_locales = true
|
||||
|
||||
config.active_record.legacy_connection_handling = false
|
||||
|
||||
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
||||
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
||||
config.time_zone = 'UTC'
|
||||
|
||||
@@ -26,8 +26,8 @@ Rails.application.configure do
|
||||
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
||||
|
||||
# Compress JavaScripts and CSS.
|
||||
config.assets.js_compressor = Uglifier.new(harmony: true)
|
||||
# config.assets.css_compressor = :sass
|
||||
config.assets.js_compressor = :terser
|
||||
# config.assets.css_compressor = :sass
|
||||
|
||||
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||
config.assets.compile = false
|
||||
|
||||
@@ -25,7 +25,7 @@ Rails.application.configure do
|
||||
config.action_controller.perform_caching = false
|
||||
|
||||
# Raise exceptions instead of rendering exception templates.
|
||||
config.action_dispatch.show_exceptions = false
|
||||
config.action_dispatch.show_exceptions = :none
|
||||
|
||||
# Disable request forgery protection in test environment.
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
# Add new mime types for use in respond_to blocks:
|
||||
# Mime::Type.register "text/richtext", :rtf
|
||||
Mime::Type.register "text/calendar", :ics
|
||||
|
||||
@@ -5,7 +5,7 @@ Rswag::Api.configure do |c|
|
||||
# This is used by the Swagger middleware to serve requests for API descriptions
|
||||
# NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
|
||||
# that it's configured to generate files in the same folder
|
||||
c.swagger_root = Rails.root.to_s + '/swagger'
|
||||
c.openapi_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
# Inject a lamda function to alter the returned Swagger prior to serialization
|
||||
# The function will have access to the rack env for the current request
|
||||
|
||||
@@ -7,5 +7,5 @@ Rswag::Ui.configure do |c|
|
||||
# NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON or YAML endpoints,
|
||||
# then the list below should correspond to the relative paths for those endpoints
|
||||
|
||||
c.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
|
||||
c.openapi_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ en:
|
||||
invalid: 'Invalid email or password.'
|
||||
invalid_token: 'Invalid authentication token.'
|
||||
timeout: 'Your session expired, please sign in again to continue.'
|
||||
inactive: 'Your account was not activated yet.'
|
||||
inactive: 'Your account is not activated.'
|
||||
sessions:
|
||||
signed_in: 'Signed in successfully.'
|
||||
signed_out: 'Signed out successfully.'
|
||||
|
||||
@@ -53,7 +53,11 @@ Rails.application.routes.draw do
|
||||
get 'author/:author' => 'posts#index', as: 'by_author', on: :collection
|
||||
end
|
||||
|
||||
resources :scientific_names
|
||||
resources :scientific_names do
|
||||
collection do
|
||||
get :gbif_suggest
|
||||
end
|
||||
end
|
||||
resources :alternate_names
|
||||
resources :plant_parts
|
||||
resources :photos
|
||||
@@ -73,6 +77,7 @@ Rails.application.routes.draw do
|
||||
get 'planted_from' => 'charts/crops#planted_from', constraints: { format: 'json' }
|
||||
get 'harvested_for' => 'charts/crops#harvested_for', constraints: { format: 'json' }
|
||||
post :openfarm
|
||||
post :gbif
|
||||
|
||||
collection do
|
||||
get 'requested'
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateMedianFunction < ActiveRecord::Migration[4.2]
|
||||
def up
|
||||
# commented out, because we upgraded the gem later and this function was removed
|
||||
# ActiveMedian.create_function
|
||||
end
|
||||
|
||||
def down
|
||||
# ActiveMedian.drop_function
|
||||
end
|
||||
end
|
||||
@@ -1,6 +0,0 @@
|
||||
class RemoveMedianFunction < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
# No longer needed, after upgrading to activemedian 0.2.0
|
||||
ActiveMedian.drop_function
|
||||
end
|
||||
end
|
||||
10
db/migrate/20240114045751_add_gbif.rb
Normal file
10
db/migrate/20240114045751_add_gbif.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddGbif < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :scientific_names, :gbif_key, :int
|
||||
add_column :scientific_names, :gbif_rank, :string
|
||||
add_column :scientific_names, :gbif_status, :string
|
||||
add_column :scientific_names, :wikidata_id, :string
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_03_13_015323) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_01_14_045751) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -543,6 +543,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_13_015323) do
|
||||
t.datetime "created_at", precision: nil
|
||||
t.datetime "updated_at", precision: nil
|
||||
t.integer "creator_id"
|
||||
t.integer "gbif_key"
|
||||
t.string "gbif_rank"
|
||||
t.string "gbif_status"
|
||||
t.string "wikidata_id"
|
||||
end
|
||||
|
||||
create_table "seeds", id: :serial, force: :cascade do |t|
|
||||
|
||||
13
env-example
13
env-example
@@ -10,7 +10,7 @@
|
||||
# include:
|
||||
# mapbox_map_id
|
||||
|
||||
# To use it, copy application.yml.example to application.yml (which is
|
||||
# To use it, copy env-example.yml or application.yml.example to application.yml (which is
|
||||
# .gitignored) and fill in the appropriate values.
|
||||
|
||||
# Settings in this file will be available to you as ENV['WHATEVER']
|
||||
@@ -59,3 +59,14 @@ GROWSTUFF_ELASTICSEARCH="true"
|
||||
GROWSTUFF_EMAIL='noreply@dev.growstuff.org'
|
||||
ELASTIC_SEARCH_VERSION="7.5.1-amd64"
|
||||
|
||||
# We also now use SMTP2GO in prod and Mailgun in staging
|
||||
# and recaptcha to solve our email issues after SendGrid stopped working
|
||||
MAILGUN_SMTP_LOGIN=""
|
||||
MAILGUN_SMTP_PASSWORD=""
|
||||
MAILGUN_SMTP_PORT=""
|
||||
MAILGUN_SMTP_SERVER=""
|
||||
# These recaptcha values are the official Google test ones from
|
||||
# https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do
|
||||
# In production, replace them with real ones
|
||||
RECAPTCHA_SITE_KEY="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
|
||||
RECAPTCHA_SECRET_KEY="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"
|
||||
|
||||
@@ -3,15 +3,14 @@
|
||||
require 'bluecloth'
|
||||
require 'haml/filters/growstuff_markdown'
|
||||
|
||||
module Haml::Filters
|
||||
module EscapedMarkdown
|
||||
include Haml::Filters::Base
|
||||
def render(text)
|
||||
Haml::Helpers.html_escape Haml::Filters::GrowstuffMarkdown.render(text)
|
||||
class Haml::Filters
|
||||
class EscapedMarkdown < Haml::Filters::GrowstuffMarkdown
|
||||
def compile(node)
|
||||
[:escape, true, super(node)]
|
||||
end
|
||||
end
|
||||
|
||||
# Register it as the handler for the :escaped_markdown HAML command.
|
||||
# The automatic system gives us :escapedmarkdown, which is ugly.
|
||||
defined['escaped_markdown'] = EscapedMarkdown
|
||||
Haml::Filters.registered[:escaped_markdown] ||= EscapedMarkdown
|
||||
end
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
require 'bluecloth'
|
||||
|
||||
module Haml::Filters
|
||||
module GrowstuffMarkdown
|
||||
include Haml::Filters::Base
|
||||
class Haml::Filters
|
||||
class GrowstuffMarkdown < Haml::Filters::Markdown
|
||||
|
||||
def render(text)
|
||||
@expanded = text
|
||||
def compile(node)
|
||||
@expanded = node.value[:text]
|
||||
expand_crops!
|
||||
expand_members!
|
||||
BlueCloth.new(@expanded).to_html
|
||||
node.value[:text] = @expanded
|
||||
compile_with_tilt(node, 'markdown')
|
||||
end
|
||||
|
||||
private
|
||||
@@ -72,5 +72,5 @@ module Haml::Filters
|
||||
|
||||
# Register it as the handler for the :growstuff_markdown HAML command.
|
||||
# The automatic system gives us :growstuffmarkdown, which is ugly.
|
||||
defined['growstuff_markdown'] = GrowstuffMarkdown
|
||||
Haml::Filters.registered[:growstuff_markdown] = GrowstuffMarkdown
|
||||
end
|
||||
|
||||
10
lib/tasks/gbif.rake
Normal file
10
lib/tasks/gbif.rake
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
namespace :gbif do
|
||||
desc "Retrieve crop info from GBIF"
|
||||
|
||||
task import: :environment do
|
||||
Rails.logger = Logger.new(STDOUT)
|
||||
GbifService.new.import!
|
||||
end
|
||||
end
|
||||
57
spec/cassettes/GbifService/_fetch/fetches_a_given_key.yml
Normal file
57
spec/cassettes/GbifService/_fetch/fetches_a_given_key.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.gbif.org/v1/species/2930137
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
headers:
|
||||
User-Agent:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
X-USER-AGENT:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
response:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
vary:
|
||||
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
pragma:
|
||||
- no-cache
|
||||
expires:
|
||||
- '0'
|
||||
x-frame-options:
|
||||
- DENY
|
||||
content-type:
|
||||
- application/json
|
||||
date:
|
||||
- Sun, 14 Jan 2024 10:03:58 GMT
|
||||
cache-control:
|
||||
- public, max-age=3601
|
||||
x-varnish:
|
||||
- 952863014 979042621
|
||||
age:
|
||||
- '126'
|
||||
via:
|
||||
- 1.1 varnish (Varnish/6.0)
|
||||
accept-ranges:
|
||||
- bytes
|
||||
content-length:
|
||||
- '938'
|
||||
connection:
|
||||
- keep-alive
|
||||
body:
|
||||
encoding: UTF-8
|
||||
string: '{"key":2930137,"nubKey":2930137,"nameKey":10463714,"taxonID":"gbif:2930137","kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"datasetKey":"d7dddbf4-2cf0-4f39-9b2a-bb099caae36c","constituentKey":"7ddf754f-d193-4cc9-b351-99906754a03b","parentKey":2928997,"parent":"Solanum","scientificName":"Solanum
|
||||
lycopersicum L.","canonicalName":"Solanum lycopersicum","vernacularName":"Garden
|
||||
tomato","authorship":"L.","nameType":"SCIENTIFIC","rank":"SPECIES","origin":"SOURCE","taxonomicStatus":"ACCEPTED","nomenclaturalStatus":[],"remarks":"","publishedIn":"L.
|
||||
(1753). In: Sp. Pl. 185.","numDescendants":23,"lastCrawled":"2023-08-22T23:20:59.545+00:00","lastInterpreted":"2023-08-22T23:12:05.487+00:00","issues":[],"class":"Magnoliopsida"}'
|
||||
recorded_at: Sun, 14 Jan 2024 10:06:05 GMT
|
||||
recorded_with: VCR 6.2.0
|
||||
103
spec/cassettes/GbifService/_suggest/matches.yml
Normal file
103
spec/cassettes/GbifService/_suggest/matches.yml
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.gbif.org/v1/species/suggest?limit=100&q=Solanum+lycopersicum
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
headers:
|
||||
User-Agent:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
X-USER-AGENT:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
response:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
vary:
|
||||
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
pragma:
|
||||
- no-cache
|
||||
expires:
|
||||
- '0'
|
||||
x-frame-options:
|
||||
- DENY
|
||||
content-type:
|
||||
- application/json
|
||||
date:
|
||||
- Sun, 14 Jan 2024 10:06:04 GMT
|
||||
cache-control:
|
||||
- public, max-age=3601
|
||||
x-varnish:
|
||||
- '993984559'
|
||||
age:
|
||||
- '0'
|
||||
via:
|
||||
- 1.1 varnish (Varnish/6.0)
|
||||
accept-ranges:
|
||||
- bytes
|
||||
content-length:
|
||||
- '10730'
|
||||
connection:
|
||||
- keep-alive
|
||||
body:
|
||||
encoding: UTF-8
|
||||
string: '[{"key":2930137,"nameKey":10463714,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930137,"scientificName":"Solanum
|
||||
lycopersicum L.","canonicalName":"Solanum lycopersicum","rank":"SPECIES","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":7815295,"nameKey":31973001,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":7815295,"parent":"Solanum","parentKey":2928997,"nubKey":7815295,"scientificName":"Solanum
|
||||
lycopersicum Blanco, 1837","canonicalName":"Solanum lycopersicum","rank":"SPECIES","status":"DOUBTFUL","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":8586238,"nameKey":6531517,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Lycopersicum
|
||||
solanum-lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":8586238,"parent":"Solanum","parentKey":2928997,"nubKey":8586238,"scientificName":"Lycopersicum
|
||||
solanum-lycopersicum Hill","canonicalName":"Lycopersicum solanum-lycopersicum","rank":"SPECIES","status":"DOUBTFUL","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":8640337,"nameKey":6531515,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Lycopersicum
|
||||
solanum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":8640337,"parent":"Solanum","parentKey":2928997,"nubKey":8640337,"scientificName":"Lycopersicum
|
||||
solanum Medik.","canonicalName":"Lycopersicum solanum","rank":"SPECIES","status":"DOUBTFUL","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":7608359,"nameKey":6531353,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":7608359,"scientificName":"Lycopersicon
|
||||
solanum-lycopersicum Hill","canonicalName":"Lycopersicon solanum-lycopersicum","rank":"SPECIES","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":11519041,"nameKey":97469846,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum
|
||||
lycopersicum","parentKey":2930137,"nubKey":11519041,"scientificName":"Solanum
|
||||
lycopersicum subsp. lycopersicum","canonicalName":"Solanum lycopersicum lycopersicum","rank":"SUBSPECIES","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":11760453,"nameKey":97469847,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum
|
||||
lycopersicum","parentKey":2930137,"nubKey":11760453,"scientificName":"Solanum
|
||||
lycopersicum subsp. cerasiforme (Alef.) Voss","canonicalName":"Solanum lycopersicum
|
||||
cerasiforme","rank":"SUBSPECIES","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":7904703,"nameKey":10463761,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum
|
||||
lycopersicum","parentKey":2930137,"nubKey":7904703,"scientificName":"Solanum
|
||||
lycopersicum var. cerasiforme (Alef.) Voss","canonicalName":"Solanum lycopersicum
|
||||
cerasiforme","rank":"VARIETY","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":11014233,"nameKey":36721070,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":11014233,"scientificName":"Solanum
|
||||
lycopersicum var. piriforme (Alef.) Voss","canonicalName":"Solanum lycopersicum
|
||||
piriforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":10798260,"nameKey":36720428,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":10798260,"scientificName":"Solanum
|
||||
lycopersicum var. ribisiodes Voss","canonicalName":"Solanum lycopersicum ribisiodes","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":2930169,"nameKey":10463787,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930169,"scientificName":"Solanum
|
||||
lycopersicum var. esculentum (Mill.) Voss","canonicalName":"Solanum lycopersicum
|
||||
esculentum","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":4274699,"nameKey":10463795,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":4274699,"scientificName":"Solanum
|
||||
lycopersicum var. lycopersicum","canonicalName":"Solanum lycopersicum lycopersicum","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":8118741,"nameKey":10463759,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":8118741,"scientificName":"Solanum
|
||||
lycopersicum var. cerasiforme (Alef.) Fosberg","canonicalName":"Solanum lycopersicum
|
||||
cerasiforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":2930179,"nameKey":10463798,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930179,"scientificName":"Solanum
|
||||
lycopersicum var. oviforme Voss","canonicalName":"Solanum lycopersicum oviforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":2930165,"nameKey":10463770,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930165,"scientificName":"Solanum
|
||||
lycopersicum var. cerasiforme (Dunal) D.M.Spooner, G.J.Anderson & R.K.Jansen","canonicalName":"Solanum
|
||||
lycopersicum cerasiforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"}]'
|
||||
recorded_at: Sun, 14 Jan 2024 10:06:04 GMT
|
||||
recorded_with: VCR 6.2.0
|
||||
108
spec/cassettes/GbifService/_update_crop/gets_photos.yml
Normal file
108
spec/cassettes/GbifService/_update_crop/gets_photos.yml
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user