* 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>
The Ruby 4.0.6 bump left the first line as `FROM 4.0.6-trixie`, which
docker resolves as an image named "4.0.6-trixie" and fails to pull.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.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>
Prevents aws-sdk-s3, aws-sdk-core, and aws-sdk-kms from auto-loading
on application boot across web and worker processes. The gem is
required on-demand in config/sitemap.rb.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com>
Prevents aws-sdk-s3, aws-sdk-core, and aws-sdk-kms from auto-loading
on application boot across web and worker processes. The gem is
required on-demand in config/sitemap.rb.
Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.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>