- Cap @member.posts in app/views/members/show.rss.haml with .limit(50)
- Cap @post.comments in app/views/posts/show.rss.haml with .limit(50) and eager load authors
- Remove eager loading of all comments+authors on Post in PostsController#show
- Add view specs to verify RSS feed limits
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Add rake tasks to pull crops and a member's data from production
crops:pull_from_production copies approved crops, and
members:pull_from_production copies one member's gardens, plantings and
planting photos, from growstuff.org into a local database. Both refuse to
run in a production environment.
They are built to be gentle on a server that has been struggling: one
request at a time with a pause between requests, exponential backoff
(honouring Retry-After) on timeouts, 429s and 5xx, and a hard stop rather
than endless retries. The client also stops if a server keeps sending the
same page, and will not follow a next link to another host.
Gardens and plantings come from the site's own JSON, which carries every
column; the JSON:API only exposes a garden's name, and its related
resource endpoints return the wrong records. Only the crops that a
member's plantings use are fetched. Flickr photos are stored as records
pointing at Flickr's image URLs, as on production, so nothing is
downloaded from Flickr.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add ACTIVE_ONLY option to the member import
ACTIVE_ONLY=true leaves out the site's all=1 flag, so production lists only
the member's active gardens and current (not finished, not failed)
plantings. The server does the filtering, so it also means fewer requests,
and no crops or photos are fetched for plantings that are skipped.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Bundle JavaScript with esbuild via jsbundling-rails
Adds React and an esbuild build (yarn build, yarn build:watch) that writes
app/assets/builds/react_islands.js, which Sprockets serves. jsbundling-rails
runs the build as part of assets:precompile, which CI and the Dockerfile
already run after yarn install.
react_islands.jsx is the entry point: it mounts a React component into every
element with data-react-component. No components are registered yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add react_component view helper
Renders an empty element carrying the component name and its props as JSON,
for react_islands.jsx to mount a React component into. Props go through
Rails' attribute escaping.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Show a React GardenSummary island on the gardens index
The first React island, to prove the pipeline end to end: server-rendered
props (owner and garden count) drive a small component with client-side
state. Loaded only on this page, deferred, via the scripts content block.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add GardenCardSerializer for the React garden cards
Turns a page of gardens into plain hashes: the garden, its owner, its
actions menu, and its active plantings split into perennials and annuals,
with progress, badges and quick actions. It mirrors what gardens/_card,
gardens/_actions and the planting partials render today, so the server
still decides labels, links and permissions.
Active plantings for the whole page are loaded in one query (with harvests
and the crop's parents), instead of several queries per garden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Render the garden cards on the index with React
The gardens index now hands GardenCardSerializer's output to a GardenCards
island instead of rendering gardens/_card per garden. The actions menu moves
to the top right of the card header. Everything else on the card is the same:
image, perennials, and each annual planting with badges, progress bar and its
own quick actions menu. The server still chooses labels, links and
permissions.
The dropdowns keep the existing Bootstrap and jquery_ujs markup
(data-bs-toggle, data-method, data-confirm), so mark inactive, delete and the
confirmations work unchanged. Menu button ids are now unique per card, so the
edit-from-index spec finds the menu by its Actions text instead of the old
repeated id.
gardens/_card stays for the activity page. This replaces the GardenSummary
island, which only proved the pipeline.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Don't raise when validating a planting that has no garden
owner_must_match_garden_owner called garden.owner unconditionally, so a
planting with a missing garden raised NoMethodError (a 500) instead of
failing the garden presence validation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Create plantings as JSON for the React garden cards
POST /plantings.json returns the garden's updated card (201), or the
validation errors (422), so the cards can show a new planting without a page
reload. It is for our own pages only: session cookie plus CSRF token, and no
CORS headers, unlike the public /api/v1.
Creating now also checks the garden is yours. Before, can :create Planting
had no garden condition and create never authorized the garden, so a crafted
request could plant into someone else's garden. This applies to the HTML form
as well. A missing garden is still left to the presence validation.
CanCan::AccessDenied now answers JSON requests with a 403 and an error,
instead of redirecting them.
The request spec logs in through a real cookie session to exercise CSRF
protection: PlantingsController uses the null_session strategy, so a request
without the token is treated as signed out (401), and the sign_in test helper
would bypass that.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Don't ask which garden when planting from a garden
Arriving at the planting form with a garden_id of one of your own gardens
(from 'Plant something here') now shows 'Planting in <garden>' with a hidden
garden field and a link to choose a different one, instead of the garden
radio buttons. Any other arrival, including a garden_id that isn't yours,
still asks as before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Open 'Plant something here' as a dialog over the garden cards
Clicking 'Plant something here' on a card opens a dialog to plant into that
garden, without leaving the list or asking which garden. Saving posts to
POST /plantings.json, swaps in the garden's updated card so the new planting
shows straight away (briefly highlighted, with a confirmation), and closes the
dialog. Errors show inside it and it stays open. A ctrl/cmd/shift-click still
opens the normal form.
The dialog is a React-controlled Bootstrap modal: focus moves in and is
trapped, Escape and the backdrop close it, and focus returns afterwards. The
crop picker searches /crops/search.json like the existing autosuggest, and the
planted-from and sun choices come from Planting's constants via props.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Use the Bootstrap 4 class names the app's CSS defines in the card components
The stylesheet is MDB 4 (Bootstrap 4), with Bootstrap 5's JavaScript on top,
so Bootstrap 5-only classes had no styling: the dialog's close button was
btn-close (a small grey box), and visually-hidden, form-select and text-start
did nothing. Use close with a times sign, sr-only, form-control and text-left,
as the app's existing modals do.
dropdown-menu-end stays, because Bootstrap 5's dropdown JavaScript reads it to
place the menu; dropdown-menu-right is added for the CSS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Make the plant-something form read as a sentence
'I planted [N] [crop](s) on [date] from [seed] in [sun].' Each blank is an
underlined inline field: quantity, the crop search, the planted date, and
planted-from and sun/shade selects. The (s) drops away when the quantity is 1.
The blanks are labelled for screen readers (Quantity, Crop, Planted date,
Planted from, Sun or shade). Notes and 'Mark as finished' stay below.
The browser spec now fills in quantity, planted from and sun as well, and
checks they are saved.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Hint at typing a crop name, and offer to request one that isn't found
The crop blank says 'type a crop name'. When a search finds nothing it says
so and links to the new-crop form (in a new tab, so the dialog stays open),
as the old planting form did.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Say 'number' in the quantity blank
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Choose a crop with the cursor keys
The crop picker is now an accessible combobox. As you type, matches appear in
a listbox: Up and Down move through them, Enter chooses the highlighted one
(or the top match if none is highlighted), and Escape closes the list before a
second Escape closes the dialog. Focus moves to 'Change' once a crop is chosen.
Matches can still be clicked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Trim the plant-something form to just the sentence
Remove 'Mark as finished' and the 'Tell us more about it' notes from the
dialog, along with the never-shown rating field it carried in its state.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Make the highlighted crop match obvious
The theme's list-group 'active' shade was barely darker than the other
matches. The one Enter would choose now has a solid blue background, white
bold text and a darker left edge.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Tidy the plant-something sentence layout
Group the sentence into phrases (I planted..., on..., from..., in...) that stay
together when it wraps, so a word is never stranded away from its blank. Hide
the quantity spinner that clipped 'number', widen the date, and give the two
selects a minimum width, with a grey 'optional' when they are still empty.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Don't let a second Enter undo a chosen crop
After choosing a crop the input is replaced by the crop's name and a Change
button. Focus went to that button, so pressing Enter again pressed it and
cleared the choice. Focus now moves on to the date blank, so a second Enter
submits the form as it would from any field.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Plant with one search and a confirmation, instead of the sentence form
The dialog asks one big question, 'What did you plant?', with the same
crop search as before (arrow keys, Enter, and a way to request a crop that
isn't found). Choosing a crop shows it back ('You're planting lettuce in
Orchard, today.') with Change and Plant it buttons, and focus lands on Plant
it so Enter confirms. The planting gets today's date and no other details;
those can be added later, which keeps adding one quick.
Drops the sentence blanks, their styles, and the planted-from/sun option lists
the page passed down.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix unreadable text on a just-planted crop
The highlight for a newly added planting set an inline pale green background
on the crop chip. Chips are brown with white text, so the name became white on
light green. The highlight is now a CSS class that leaves the chip's colours
alone: a soft green row with a left bar for annual plantings, and a green ring
around the chip for perennials. The browser spec checks the chip carries no
inline style.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Tidy the plant dialog and show what is happening at each stage
Standard patterns so the dialog always says where you are and what it is doing:
- a two-step indicator (Choose a crop, Confirm) with a tick on the finished step
- a spinner in the search box while searching, plus a 'Searching...' status, a
count of matches, and an info icon on the 'no crops match' note
- the choice shown back as a Crop / Garden / When summary with a Change button
- Plant it as a filled green primary button that stays green while focused (the
theme greys a focused button) and shows a spinner while planting, with Cancel
and Change disabled meanwhile
- an error panel with a warning icon that says the choice is kept for retrying
- the matches listed in the dialog rather than floating over its buttons
- a tick on the 'Planted ...' notice on the cards
- the shovel (spade-marker.svg, the map marker) in the dialog title
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Build the React bundle in CI and for fresh checkouts
app/assets/builds/react_islands.js is built by esbuild and not committed, so on
a fresh checkout pages that load it (the gardens index) fail with 'The asset
react_islands.js is not present in the asset pipeline'. The rspec CI job never
built it: only the browser-feature jobs did, through assets:precompile. Add a
yarn build step there, and have the specs build the bundle once if it is
missing so a local checkout doesn't fail either.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add the missing 'View' translation
The planting quick-actions menu used t('view'), which has no translation. In a
view Rails hides that by showing a humanised 'View'; the card serializer uses
I18n.t, which showed 'Translation missing: en.view' in the menu. Add
buttons.view, use it in both places, and add a serializer spec that fails if
any card label is a missing translation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix two crop_jsonld_data specs that could not pass
The helper builds its hash with symbol keys, but the spec read 'data["@context"]'
with a string key. And 'caps posts and photos at 50' relied on
create(:post, crops: [crop]), which creates no crop_posts rows here, and on
crop.photo_associations.create!, which the Crop model's own association leaves
without a photographable. Link posts with CropPost and photos with
PhotoAssociation explicitly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Run the helper and serializer specs in CI
The rspec job ran lib, services, models, controllers, views, routing and
requests, but not spec/helpers or spec/serializers, so those specs (including
the react_component helper and the card serializer) never ran there, and two
broken crops_helper specs went unnoticed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Tidy the garden cards
The cards were cluttered and misaligned. Now:
- the header has a smaller title with the owner under it, and the menu top right
- a smaller picture, and 'Perennials' and 'Annuals' headings instead of a
divider and a gap; 'Nothing planted here yet.' for an empty garden
- each annual planting is a row of three aligned columns: the crop with when it
was planted (which tells duplicates apart), then its badges and progress bar,
then a labelled three-dots menu in place of a bare chevron
- the note when there is nothing to predict from is one short muted line that
no longer repeats the crop name
- on a phone the picture and rows stack
Also restores the just-planted highlight styles, which a stylesheet edit had
dropped, and adds a spec that the highlight has a background, since the class
existing was all that was checked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Make the planting menu button compact
The theme gives every .btn a 5.5rem minimum width, so the three-dots button's
hover background was a wide grey box. It is now a 2.25rem round button.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
In `CropsController#data_improvement`, paginate `@crops` with
`per_page: 50` so tabs like photos/descriptions do not render all
crops at once. In `data_improvement.html.haml`, render `= will_paginate @crops`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Use `.distinct` when fetching `planters_ids` to avoid duplicate member
IDs and replace `photos.size` with the `photo_associations_count`
counter cache column for `has_photos`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
When initializing a new harvest from a planting (via `planting_slug`
or `harvest[planting_id]`), `HarvestsController#new` now properly looks
up the planting, assigns `@harvest.planting` and `@harvest.crop` from
the planting's crop, and sets `@planting` and `@crop` instance variables
for view rendering.
Regression tests added to `HarvestsController` spec and request spec.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Add rake tasks to pull crops and a member's data from production
crops:pull_from_production copies approved crops, and
members:pull_from_production copies one member's gardens, plantings and
planting photos, from growstuff.org into a local database. Both refuse to
run in a production environment.
They are built to be gentle on a server that has been struggling: one
request at a time with a pause between requests, exponential backoff
(honouring Retry-After) on timeouts, 429s and 5xx, and a hard stop rather
than endless retries. The client also stops if a server keeps sending the
same page, and will not follow a next link to another host.
Gardens and plantings come from the site's own JSON, which carries every
column; the JSON:API only exposes a garden's name, and its related
resource endpoints return the wrong records. Only the crops that a
member's plantings use are fetched. Flickr photos are stored as records
pointing at Flickr's image URLs, as on production, so nothing is
downloaded from Flickr.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add ACTIVE_ONLY option to the member import
ACTIVE_ONLY=true leaves out the site's all=1 flag, so production lists only
the member's active gardens and current (not finished, not failed)
plantings. The server does the filtering, so it also means fewer requests,
and no crops or photos are fetched for plantings that are skipped.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Optimize PaperTrail whodunnit query in CropsController#show
Replace @crop.versions.map(&:whodunnit) with
@crop.versions.distinct.pluck(:whodunnit) to avoid instantiating all PaperTrail version objects and loading object/object_changes into memory.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Optimize PaperTrail whodunnit query in CropsController#show
Replace @crop.versions.map(&:whodunnit) with
@crop.versions.distinct.pluck(:whodunnit) to avoid instantiating all PaperTrail version objects and loading object/object_changes into memory. Added unit test in spec/controllers/crops_controller_spec.rb.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Optimize PaperTrail whodunnit query in CropsController#show
Use @crop.versions.reorder(nil).distinct.pluck(:whodunnit) to avoid instantiating all PaperTrail version objects into memory and prevent PostgreSQL PG::InvalidColumnReference errors with SELECT DISTINCT.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Fix crop route parameter in controller spec
Update parameter key from id to slug in CropsController show spec to match param: :slug route definition.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Update liked_by? to return false if member is nil and use likes.exists?(member_id: member.id) instead of allocating login name arrays.
- Simplify liked_by_members_names to use members.pluck(:login_name).
- Update app/views/photos/_likes.html.haml to use photo.liked_by?(current_member).
- Add unit tests for liked_by? in spec/models/like_spec.rb.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Keep Rack::Attack counters in process memory
Rack::Attack was storing its request counters in Rails.cache, which is
memcached in production. On 2026-09-19, memcached calls were timing out
in the production logs. When that happens, the counters stop working.
In the same log window, one IP made 753 requests. 717 of them
succeeded. The 15 per minute throttle and the 500 per day ban did not
stop it.
Rack::Attack now uses its own in-memory store, capped at 8 MB. Each
Puma worker keeps its own counters, so the limits apply per worker.
Refs #1640
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add BLOCKED_IPS list to Rack::Attack
When one crawler overloads the site, we need a fast way to block it.
Rack::Attack now blocks every IP listed in the BLOCKED_IPS environment
variable. The list is comma separated.
Setting the variable does not need a code change. It also keeps IP
addresses out of the repository.
Refs #1640
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Lower the memcached socket timeout to 0.5 seconds
The production cache store waited up to 1.5 seconds for each memcached
call. Dalli's own default is 1 second. Dalli also retries after a
failure, so one bad cache call can block a Puma thread for longer than
the timeout.
On 2026-09-19, the production logs showed memcached timeouts every few
seconds. With only 5 threads per Puma worker, blocked threads make the
site slow. Slow requests hold memory for longer.
The timeout is now 0.5 seconds. A healthy memcached call takes a few
milliseconds. A call that takes 0.5 seconds has failed in practice, so the
request is better off skipping the cache.
Refs #1640
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Wait for the page reload in the members list spec
The "members list" feature specs click "Show" and then read the list of
members right away. "Show" submits a GET form, so the browser loads a
whole new page. The spec could read the list from the old page. The old
page was then replaced, and Selenium raised StaleElementReferenceError.
This failed once in CI on the pull request for #4800:
https://github.com/Growstuff/growstuff/actions/runs/35477162596
Both examples now wait for the new URL before they read the list. The
URL contains the chosen sort order, so the wait only ends once the new
page has loaded.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Wait for the place page in the places search spec
The "User searches" specs fill in the search form, click "Search", and
then read the page straight away. The search goes to /places/search,
which redirects to /places/<place>. The spec could read the page while
the browser was still moving to the new page.
Selenium then raised "Node with given id does not belong to the
document". It failed in CI on the first example in "with a valid place":
https://github.com/Growstuff/growstuff/actions/runs/35481754102
The search_with helper now waits for the place page URL before it
returns. A blank search stays on the same URL, so it does not wait.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Rename the places CI workflow from "Admin" to "Places"
The workflow in ci-features-places.yml was named "CI Features - Admin".
It looks like it was copied from the admin workflow and never renamed.
It runs spec/features/places/.
Two workflows then had the same name. Every commit showed two
"CI Features - Admin" runs. A failing places spec appeared under the
admin name, which sent people to the wrong specs:
https://github.com/Growstuff/growstuff/actions/runs/35481754102
Branch protection requires a check called "rspec". That is the job
name, not the workflow name, so the required check does not change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
- Configure Allow2Ban in Rack::Attack to ban IPs requesting >500 pages per day for 1 week (7 days).
- Add honeypot route /dont-crawl-me disallowed in robots.txt and configure Fail2Ban in Rack::Attack to ban IPs visiting it for 7 days upon 1 hit.
- Update PhotosController#index to raise ActiveRecord::RecordNotFound when page parameter is out of bounds, returning 404 Not Found to crawlers instead of 200 OK.
- Add request and controller specs for Rack::Attack rules and pagination 404 responses.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Implement crop photo fallback in PhotoCapable thumbnail_url
Currently, plantings, seeds, and harvests display "no photo available" placeholders when they do not have specific photos attached. This change enables the PhotoCapable#thumbnail_url method to automatically fall back to the associated crop's thumbnail_url when no item-specific photo is present.
Additionally, this commit:
- Fixes validation in PhotoAssociation to handle crop photos that do not have a polymorphic photographable owner.
- Disables cache store during testing to prevent stale/cached default photos from leaking across specs.
- Adds comprehensive unit tests for the fallback logic.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Keep only core crop photo fallback and unit tests, removing excess changes
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Upgrade application to Rails 8.1
- Update rails gem to ~> 8.1.0
- Set config.load_defaults 8.1 in application.rb
- Add gem 'csv' for Ruby 3.4+ compatibility
- Replace deprecated 'render text:' with 'render plain:' in PagesController
- Add compatibility patch for jsonapi-resources routing in Rails 8.1
- Add compatibility patch for Faraday 2.x error constants
- Add Searchkick test stubs for environments without Elasticsearch
- Include required Active Storage update migrations
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Remove disabled searchkick
* Apply suggestion from @CloCkWeRX
* Delete config/initializers/faraday_patch.rb
* Apply suggestions from code review
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
* Remove modifications for tests without elasticsearch
* Remove psych gem and its dependencies
Removed psych gem version 5.4.0 and its dependencies.
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Refactor Plantings to remove Elasticsearch dependency
This commit refactors the Plantings controller, model, and views to use
ActiveRecord instead of Searchkick/Elasticsearch.
Key changes:
- Refactored `PlantingsController#index` to use an ActiveRecord-based
`plantings` method.
- Moved `homepage_records` logic from `SearchPlantings` concern to the
`Planting` model using ActiveRecord scopes.
- Removed `SearchPlantings` concern and deleted the file.
- Updated `home/_plantings.html.haml` and `plantings/index.rss.haml` to
use dot notation for `Planting` object attributes.
- Updated specs to remove Searchkick-related setup and assertions.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Fix Planting.reindex
* Fix test
* Mark test pending
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Refactor Seeds to remove Elasticsearch dependency
This commit refactors the `SeedsController` and `Seed` model to remove
dependency on Elasticsearch and Searchkick.
Key changes:
- Removed `SearchSeeds` concern from the `Seed` model.
- Reimplemented `Seed.homepage_records` using ActiveRecord.
- Refactored `SeedsController#index` to use ActiveRecord for filtering,
eager loading, and pagination.
- Deleted `app/models/concerns/search_seeds.rb`.
- Updated relevant specs to remove Searchkick-specific tags and setup.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Remove Seed.reindex
* Remove trait
* Fix tests
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
The Photo model is no longer using Searchkick/Elasticsearch, so calling
`Photo.reindex` causes a NoMethodError. This commit removes these calls
from `spec/spec_helper.rb` and feature specs, and removes the unused
`:reindex` trait from the photo factory call in `likeable_spec.rb`.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
- Refactored `PhotosController#index` to use ActiveRecord queries instead of Searchkick/Elasticsearch.
- Handled filtering by crop and planting via associations.
- Added eager loading for the photo owner to prevent N+1 queries.
- Decoupled the `Photo` model from Elasticsearch by removing the `SearchPhotos` concern.
- Deleted `app/models/concerns/search_photos.rb`.
- Updated controller and model specs to use standard ActiveRecord expectations.
- Cleaned up the photo factory by removing the now-obsolete `:reindex` trait.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
- Update Ability to grant destroy permission to collaborators for their own record
- Add 'Leave garden' link to garden show page
- Add specs for garden collaborator permissions
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Add crops search API endpoint
- Added GET /api/v1/crops/search endpoint.
- Updated CropSearchService to support additional search options.
- Manually updated Swagger documentation in swagger/v1/swagger.json.
- Added request specs to verify the new endpoint.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Add crops search API endpoint
- Added GET /api/v1/crops/search endpoint.
- Updated CropSearchService to support additional search options.
- Manually updated Swagger documentation in swagger/v1/swagger.json.
- Added request specs to verify the new endpoint.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Update planting rating when recording a harvest
- Added virtual attribute `overall_rating` to `Harvest` model.
- Updated `HarvestsController` to permit `overall_rating` and synchronize it to the associated `Planting`.
- Added a rating range field (1-5) to the harvest form.
- Added controller tests to verify that the planting rating is updated.
- Refined feature tests for harvesting.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* I have updated the system to allow for recording a planting rating when a harvest is logged. Here is a summary of the changes:
- Added a virtual attribute `overall_rating` to the `Harvest` model.
- Updated `HarvestsController` to permit `overall_rating` and synchronize it to the associated `Planting`.
- Added a rating range field (1-5) to the harvest form.
- Added controller tests to verify that the planting rating is updated correctly.
- Updated feature tests to ensure the harvest form functions as expected.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Update database.yml
* Apply suggestions from code review
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
* Adjust wording
* Change harvest modal
* Fix tests
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Added `send_harvest_reminder` preference to Member model and settings UI.
- Implemented `harvest_in_next_week?` in PredictHarvest concern.
- Created `harvest_reminder` email with localized templates.
- Added `growstuff:send_harvest_reminders` Rake task to run weekly.
- Refactored existing and new reminder tasks to use `deliver_later` for scalability.
- Added unit tests for prediction logic and mailer.
- Fixed a bug in the existing planting reminder task where it was using an uninitialized constant `Notifier`.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Refactor Activity model to remove Elasticsearch integration
- Removed `SearchActivities` concern and Searchkick from `Activity` model.
- Implemented `Activity.homepage_records` using ActiveRecord with `DISTINCT ON` for PostgreSQL.
- Updated `ActivitiesController#index` to use ActiveRecord queries with eager loading and pagination.
- Added `active` scope to `Activity`.
- Added unit tests for `Activity` model.
- Deleted `app/models/concerns/search_activities.rb`.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Fix NoMethodError: undefined method 'reindex' for class Activity
- Removed all calls to `Activity.reindex` in migrations, rake tasks, and spec helpers.
- These were causing failures after the removal of Searchkick from the Activity model.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Fix ambiguous column id in homepage_records query
- Updated `Activity.homepage_records` to use `activities.id` instead of `id` in the subquery.
- This resolves the `PG::AmbiguousColumn: ERROR: column reference "id" is ambiguous` error.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Fix ambiguous created_at in homepage_records query
- Use `unscoped` in the subquery for `Activity.homepage_records` to bypass the default scope from `Ownable` concern.
- This prevents the join with the `members` table in the subquery, which was causing `PG::AmbiguousColumn: ERROR: column reference "created_at" is ambiguous`.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Complete refactoring of Activity model to remove Elasticsearch
- Removed SearchActivities concern and searchkick integration.
- Updated ActivitiesController#index to use ActiveRecord queries.
- Implemented performant Activity.homepage_records using DISTINCT ON (PostgreSQL).
- Added Activity.active scope.
- Added no-op Activity.reindex (class and instance methods) for backward compatibility.
- Cleaned up leftover reindex calls in rake tasks, migrations, and spec helpers.
- Added unit tests for new Activity model logic.
- Updated factories to include no-op reindex traits.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Less eager loading
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Update MemberFlickr concern to support tag-based search using flickr.photos.search
- Update PhotosController to handle the 'tag' parameter
- Add tag search input field to the 'New Photo' view
- Add test case to verify tag filtering in PhotosController spec
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
- Added Rails.cache.fetch to `sunniness` and `planted_from` actions.
- Refactored crop loading into a `before_action :set_crop`.
- Updated specs to verify caching behavior and ensure coverage.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Add members:cleanup_inactive rake task
This task identifies and deletes members who have not logged in for over
24 months and have no gardens, plantings, or other activity (posts,
comments, seeds, harvests, etc).
Includes support for DRY_RUN=true to preview deletions.
Added tests in spec/tasks/members_spec.rb.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Refactor activity check to Member#has_activity? and update rake task
- Added `Member#has_activity?` to encapsulate the check for gardens, plantings, and other activity.
- Updated `members:cleanup_inactive` rake task to use `Member#has_activity?`.
- Maintained `DRY_RUN` support and existing tests.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Apply suggestion from @CloCkWeRX
* Apply suggestions from code review
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Implement blocking feature
This commit introduces a blocking feature that allows members to block other members.
A blocked member is prevented from:
- following the blocker
- sending private messages to the blocker
- replying to the blocker's posts
- liking the blocker's content
The implementation includes:
- A new `Block` model and a corresponding database table.
- Updates to the `Member` model to include associations for blocks.
- A new `BlocksController` to handle blocking and unblocking actions.
- New routes for the `BlocksController`.
- UI changes to add block/unblock buttons to the member profile page.
- Validations in the `Follow`, `Comment`, and `Like` models to enforce the blocking rules.
- A check in the `MessagesController` to prevent sending messages to a member who has blocked the sender.
- A callback in the `Block` model to destroy the follow relationship when a block is created.
- New feature and model specs to test the blocking functionality.
* Implement blocking feature and fix failing tests
This commit introduces a blocking feature that allows members to block other members.
A blocked member is prevented from:
- following the blocker
- sending private messages to the blocker
- replying to the blocker's posts
- liking the blocker's content
The implementation includes:
- A new `Block` model and a corresponding database table.
- Updates to the `Member` model to include associations for blocks.
- A new `BlocksController` to handle blocking and unblocking actions.
- New routes for the `BlocksController`.
- UI changes to add block/unblock buttons to the member profile page.
- Validations in the `Follow`, `Comment`, and `Like` models to enforce the blocking rules.
- A check in the `MessagesController` to prevent sending messages to a member who has blocked the sender.
- A callback in the `Block` model to destroy the follow relationship when a block is created.
- New feature and model specs to test the blocking functionality.
This commit also fixes a failing test in the blocking feature. The error was caused by the validation being called even when the `member` association was `nil`. A guard has been added to the validation methods in the `Like`, `Follow`, and `Comment` models to prevent this from happening.
* Generate schema
* Fix tests
* Add permissions
* Define Block permissions in Ability model
The feature specs for member blocking were failing because the "Block"
link was not being rendered on member profiles. This was due to the
lack of explicit create and destroy permissions for the Block resource
in the Ability model, which is used by CanCanCan to authorize actions
and by the view to conditionally show links.
This change adds the necessary permissions to `member_abilities`:
- Allows members to create blocks (except for blocking themselves).
- Allows members to destroy blocks where they are the blocker.
These rules ensure that the "Block" and "Unblock" links are correctly
rendered and authorized for signed-in members.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Comment out specs for now
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Add comprehensive test coverage for forums
- Added `spec/controllers/forums_controller_spec.rb` to test all CRUD actions and authorization for guest, member, and admin roles.
- Added `spec/features/forums_spec.rb` to cover user-facing features such as browsing forums and creating posts from within a forum.
- Updated `spec/requests/forums_spec.rb` to cover basic request flow and JSON response formats.
Note: Tests were verified for content and logic but execution in the sandbox environment was blocked by missing infrastructure (PostgreSQL and Elasticsearch).
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Fix specs
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Move expectations from `before` hooks to `it` blocks.
- Ensure controller actions are called after expectations are set in controller specs.
- Replace synchronization expectations in hooks with Capybara `find` calls.
- Remove RSpec/ExpectInHook from .rubocop_todo.yml.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Fix RSpec/IndexedLet RuboCop issues in spec files
Replace indexed let variable names with descriptive names across 11 spec files.
This improves readability and complies with the RSpec/IndexedLet rule.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
* Rubocop
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>