Compare commits

...

149 Commits

Author SHA1 Message Date
google-labs-jules[bot]
1b087bd161 Refactor API request specs to use token authentication
This commit introduces a new shared context, `with authenticated member`, to standardize authentication in the API request specs. The shared context creates a member, generates an API token using the `Token token=` scheme, and provides authenticated and unauthenticated headers for use in the tests.

All API request specs in `spec/requests/api/v1/` have been updated to use this shared context. This includes:

*   Associating test data with the authenticated member to correctly test authorization scopes.
*   Updating test expectations to reflect the new scoped behavior of the API endpoints.
*   Refactoring `create`, `update`, and `delete` tests to use the shared headers for checking authorization rules, making the tests cleaner and more robust.
2025-11-29 03:41:08 +00:00
dependabot[bot]
e9a187b3df Merge pull request #4282 from Growstuff/dependabot/bundler/icalendar-2.12.0 2025-09-29 10:54:38 +00:00
dependabot[bot]
7b7bf9f4e9 Bump icalendar from 2.11.2 to 2.12.0
Bumps [icalendar](https://github.com/icalendar/icalendar) from 2.11.2 to 2.12.0.
- [Changelog](https://github.com/icalendar/icalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/icalendar/icalendar/compare/v2.11.2...v2.12.0)

---
updated-dependencies:
- dependency-name: icalendar
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 08:35:09 +00:00
Daniel O'Connor
d9851231f2 Merge pull request #4274 from Growstuff/CloCkWeRX-patch-3
API > Render activities links under member
2025-09-29 01:11:24 +09:30
Daniel O'Connor
f82eabec42 Merge pull request #4273 from Growstuff/CloCkWeRX-patch-2
Create robots.txt based on wikipedia
2025-09-29 01:03:42 +09:30
Daniel O'Connor
83bf752a02 Merge pull request #4272 from Growstuff/feature/JULES-38-planting-api-garden-id
Adjust all API resources to include basic data for has one relationships
2025-09-29 00:55:18 +09:30
dependabot[bot]
6ffdd283e4 Merge pull request #4270 from Growstuff/dependabot/bundler/rack-2.2.18 2025-09-28 15:19:22 +00:00
Daniel O'Connor
d8e138ae2d Create robots.txt based on wikipedia 2025-09-29 00:27:57 +09:30
Daniel O'Connor
81b80d9bb7 Update activity_resource.rb 2025-09-29 00:12:51 +09:30
Daniel O'Connor
f080a8a566 Update crop_resource.rb 2025-09-29 00:12:39 +09:30
Daniel O'Connor
5a436f9d7f Update garden_resource.rb 2025-09-29 00:12:32 +09:30
Daniel O'Connor
a4fd1c4a8e Update harvest_resource.rb 2025-09-29 00:12:27 +09:30
Daniel O'Connor
7277cb3523 Update seed_resource.rb 2025-09-29 00:12:07 +09:30
Daniel O'Connor
522d10e053 Update photo_resource.rb 2025-09-29 00:11:41 +09:30
Daniel O'Connor
f3a9b26c8e Update planting_resource.rb 2025-09-29 00:10:05 +09:30
google-labs-jules[bot]
d8f5580ef9 Extend planting API to render garden id in relationships 2025-09-28 14:39:33 +00:00
dependabot[bot]
d9e58f6bf0 Bump rack from 2.2.17 to 2.2.18
Bumps [rack](https://github.com/rack/rack) from 2.2.17 to 2.2.18.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v2.2.17...v2.2.18)

---
updated-dependencies:
- dependency-name: rack
  dependency-version: 2.2.18
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-26 09:46:59 +00:00
Daniel O'Connor
60ec4e8d18 Merge pull request #4271 from Growstuff/dependabot/bundler/rubocop-1.81.0
Bump rubocop from 1.80.2 to 1.81.0
2025-09-26 19:15:45 +09:30
dependabot[bot]
ede7e6e7f7 Bump rubocop from 1.80.2 to 1.81.0
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.80.2 to 1.81.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.80.2...v1.81.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.81.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-26 07:01:42 +00:00
dependabot[bot]
9c62f955e2 Merge pull request #4267 from Growstuff/dependabot/bundler/geocoder-1.8.6 2025-09-24 09:47:54 +00:00
dependabot[bot]
6b313c190f Bump geocoder from 1.8.5 to 1.8.6
Bumps [geocoder](https://github.com/alexreisner/geocoder) from 1.8.5 to 1.8.6.
- [Changelog](https://github.com/alexreisner/geocoder/blob/master/CHANGELOG.md)
- [Commits](https://github.com/alexreisner/geocoder/compare/v1.8.5...v1.8.6)

---
updated-dependencies:
- dependency-name: geocoder
  dependency-version: 1.8.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-24 09:15:25 +00:00
Daniel O'Connor
c575f9d03c Merge pull request #4269 from Growstuff/dependabot/bundler/puma-7.0.4
Bump puma from 7.0.3 to 7.0.4
2025-09-24 18:44:13 +09:30
dependabot[bot]
95a4b0c66c Bump puma from 7.0.3 to 7.0.4
Bumps [puma](https://github.com/puma/puma) from 7.0.3 to 7.0.4.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v7.0.3...v7.0.4)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 7.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-24 07:01:40 +00:00
Daniel O'Connor
488e402a3a Adjust copy of activities and prompts (#4260)
* Adjust copy

* Adjust copy

* Move repeat prompt

* Improve prompts

* Clean up schema

* Fix spec
2025-09-21 14:58:13 +09:30
google-labs-jules[bot]
e99c90adc4 Add recurring activities feature without database persistence (#4261)
* This change introduces a new feature that allows users to create recurring activities.

A user can now specify that an activity should be repeated "X" times, every "Y" weeks. When an activity is created with these options, the application will create the initial activity and then "X" additional copies, with each copy's due date offset by "Y" weeks from the previous one.

The repeat information is not stored in the database. It is only used at the time of creation to generate the recurring activities.

The following changes were made:
- Updated the new activity form to include fields for "repeat times" and "repeat weeks".
- Modified the `ActivitiesController#create` action to handle the creation of recurring activities.
- Added feature tests to ensure the new functionality works as expected.

* Remove not very useful spec

---------

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>
2025-09-21 13:53:45 +09:30
google-labs-jules[bot]
41412b8443 Merge pull request #4262 from Growstuff/feature/github-releases-widget
feat: Add cached GitHub releases widget to homepage
2025-09-21 13:37:57 +09:30
Daniel O'Connor
b08069df46 Merge pull request #4259 from Growstuff/feature/companion-plantings
Add ability to create companion plantings
2025-09-21 10:37:52 +09:30
google-labs-jules[bot]
449ab1f6c0 Add ability to create companion plantings
This change adds the ability for crop wranglers to create and manage companion plantings for crops.

- Adds a `source_url` to the `CropCompanion` model to store an optional reference URL.
- Restricts the management of companion plantings to users with the `crop_wrangler` role.
- Creates a new admin interface for managing companion plantings for a specific crop.
- Updates the crop show page to display companions from both the crop and its parent crop.
2025-09-21 00:49:11 +00:00
Daniel O'Connor
0e52b8c00b Merge pull request #4256 from Growstuff/more-rubocop
More rubocop
2025-09-20 20:16:09 +09:30
Daniel O'Connor
bd4dd3cc45 Rubocop 2025-09-20 10:18:24 +00:00
Daniel O'Connor
a098328e4e Rubocop 2025-09-20 10:16:16 +00:00
Daniel O'Connor
04680b196a Rubocop 2025-09-20 10:15:41 +00:00
Daniel O'Connor
d542575874 Rubocop 2025-09-20 10:15:28 +00:00
Daniel O'Connor
5418cad976 Rubocop 2025-09-20 10:15:14 +00:00
Daniel O'Connor
547e4f843b Merge pull request #4255 from Growstuff/rubocop-fixes
Rubocop fixes
2025-09-20 19:44:42 +09:30
Daniel O'Connor
ce946c3a83 Rubocop fixes 2025-09-20 09:45:56 +00:00
Daniel O'Connor
54acc369ab Rubocop 2025-09-20 09:43:22 +00:00
Daniel O'Connor
6df1d9d247 Merge pull request #4254 from Growstuff/feature/copy-activity
Add ability to copy/duplicate an activity
2025-09-20 19:06:43 +09:30
Daniel O'Connor
154d6e9022 Merge branch 'dev' into feature/copy-activity 2025-09-20 18:48:23 +09:30
Daniel O'Connor
dadaddccfb Merge pull request #4250 from Growstuff/dependabot/bundler/active_utils-3.6.0
Bump active_utils from 3.5.0 to 3.6.0
2025-09-20 18:44:51 +09:30
Daniel O'Connor
85f508b3a7 Merge pull request #4251 from Growstuff/dependabot/bundler/rexml-3.4.2
Bump rexml from 3.4.1 to 3.4.2
2025-09-20 18:44:39 +09:30
Daniel O'Connor
2e517519a1 Merge pull request #4252 from Growstuff/dependabot/bundler/recaptcha-5.21.1
Bump recaptcha from 5.20.1 to 5.21.1
2025-09-20 18:44:25 +09:30
google-labs-jules[bot]
5db6a86607 Add ability to copy/duplicate an activity 2025-09-20 08:54:12 +00:00
Daniel O'Connor
850687e80f Bump puma from 7.0.2 to 7.0.3 (#4249)
Bumps [puma](https://github.com/puma/puma) from 7.0.2 to 7.0.3.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v7.0.2...v7.0.3)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 7.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
2025-09-20 18:17:31 +09:30
Daniel O'Connor
1531c6e69b Merge branch 'dev' into dependabot/bundler/puma-7.0.3 2025-09-20 18:17:23 +09:30
Daniel O'Connor
ce7ce70d5f Merge pull request #4222 from Growstuff/feature/add-activity-prompts
feat: Add prompts for new activities
2025-09-20 17:49:47 +09:30
dependabot[bot]
600e61a282 Bump recaptcha from 5.20.1 to 5.21.1
Bumps [recaptcha](https://github.com/ambethia/recaptcha) from 5.20.1 to 5.21.1.
- [Changelog](https://github.com/ambethia/recaptcha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ambethia/recaptcha/compare/v5.20.1...v5.21.1)

---
updated-dependencies:
- dependency-name: recaptcha
  dependency-version: 5.21.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-18 07:01:16 +00:00
dependabot[bot]
aa7641ad91 Bump rexml from 3.4.1 to 3.4.2
Bumps [rexml](https://github.com/ruby/rexml) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.4.1...v3.4.2)

---
updated-dependencies:
- dependency-name: rexml
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-17 19:11:30 +00:00
dependabot[bot]
64acd4c00c Bump active_utils from 3.5.0 to 3.6.0
Bumps [active_utils](https://github.com/shopify/active_utils) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/shopify/active_utils/releases)
- [Changelog](https://github.com/Shopify/active_utils/blob/main/CHANGELOG.md)
- [Commits](https://github.com/shopify/active_utils/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: active_utils
  dependency-version: 3.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-16 07:01:15 +00:00
dependabot[bot]
d95ffdbef6 Bump puma from 7.0.2 to 7.0.3
Bumps [puma](https://github.com/puma/puma) from 7.0.2 to 7.0.3.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v7.0.2...v7.0.3)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 7.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 07:38:14 +00:00
Daniel O'Connor
6d076983dd Merge pull request #4245 from Growstuff/swagger-format
Format
2025-09-12 17:41:23 +09:30
Daniel O'Connor
4dfe325077 Merge branch 'dev' into swagger-format 2025-09-12 08:12:01 +09:30
Daniel O'Connor
a76ef6a117 Format 2025-09-10 13:56:29 +00:00
Daniel O'Connor
99478e3920 Rubocop (#4242) 2025-09-10 20:46:12 +09:30
Daniel O'Connor
a2f05097af Merge branch 'mainline' into dev 2025-09-10 20:02:31 +09:30
Daniel O'Connor
e5bf9d98e6 Rubocop (#4241) 2025-09-10 19:56:12 +09:30
Daniel O'Connor
7988080054 Update .rubocop.yml 2025-09-10 19:52:44 +09:30
google-labs-jules[bot]
02db5b8130 Add API token generation, authentication, and CRUD for a number of the API resources (#4237)
* feat: Add API token generation and authentication

This commit introduces API token generation and authentication for write operations.

- Adds a section to the user's profile edit page to generate and display an API token.
- Reuses the `authentications` table to store the API token, avoiding the need for a database migration.
- Implements token-based authentication for the API using the `Authorization: Token token=...` header.
- Enables write operations for all API resources and ensures they are protected by the new authentication mechanism.
- Adds feature and request specs to test the new functionality.

* feat: Add API token generation and authentication

This commit introduces API token generation and authentication for write operations.

- Adds a section to the user's profile edit page to generate and display an API token.
- Reuses the `authentications` table to store the API token, avoiding the need for a database migration.
- Implements token-based authentication for the API using the `Authorization: Token token=...` header.
- Enables write operations for all API resources and ensures they are protected by the new authentication mechanism.
- Adds feature and request specs to test the new functionality.

* Mark as editable

* Refactor

* WIP - Authentication

* Implement more test coverage

* Split 401 and 403

* Before Create hooks

* Update harvest specs, defaulting to the first plant part - this may not be right

* Update coverage

* Update coverage

* Rubocop

* Rubocop

* Rubocop

* Fix coverage

* For now, mark photos immutable again

* Fix specs

* Fix specs

* Rubocop

* Fix specs

---------

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>
2025-09-10 19:50:06 +09:30
Daniel O'Connor
cf8380029a Rubocop 2025-09-10 10:19:08 +00:00
Daniel O'Connor
eefda21d1a Merge pull request #4226 from Growstuff/dev
Release 70
2025-09-09 22:23:27 +09:30
Daniel O'Connor
4c0a63bd28 Merge pull request #4235 from Growstuff/activity_name
Fix UX
2025-09-09 22:20:06 +09:30
Daniel O'Connor
7f19891428 Merge branch 'dev' into activity_name 2025-09-09 22:19:57 +09:30
Daniel O'Connor
e322871740 Fix UX 2025-09-09 12:49:17 +00:00
Daniel O'Connor
35f18556fd Merge pull request #4234 from Growstuff/activity_name
Delegate
2025-09-09 22:16:41 +09:30
Daniel O'Connor
85034298ec Merge branch 'dev' of https://github.com/Growstuff/growstuff into activity_name 2025-09-09 12:45:14 +00:00
Daniel O'Connor
b2e959aded Delegate 2025-09-09 12:44:40 +00:00
Daniel O'Connor
a4e2bf5d54 Activity name (#4233)
* Add aliases

* Add aliases
2025-09-09 22:11:18 +09:30
Daniel O'Connor
9cd00b44bb Merge branch 'dev' into activity_name 2025-09-09 22:11:06 +09:30
Daniel O'Connor
2f0b8e9d76 Add aliases 2025-09-09 12:40:06 +00:00
Daniel O'Connor
bb4e2dd788 Add aliases (#4232) 2025-09-09 22:07:24 +09:30
Daniel O'Connor
fb78bcb0b0 Add aliases 2025-09-09 12:36:22 +00:00
google-labs-jules[bot]
e5c71f1dc4 Merge pull request #4230 from Growstuff/feature/add-more-filters
feat: Add more filters to API resources
2025-09-09 21:34:49 +09:30
dependabot[bot]
2d62891ef0 Merge pull request #4227 from Growstuff/dependabot/bundler/factory_bot_rails-6.5.1 2025-09-09 09:04:42 +00:00
dependabot[bot]
cf840582d5 Bump factory_bot_rails from 6.5.0 to 6.5.1
Bumps [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails) from 6.5.0 to 6.5.1.
- [Release notes](https://github.com/thoughtbot/factory_bot_rails/releases)
- [Changelog](https://github.com/thoughtbot/factory_bot_rails/blob/main/NEWS.md)
- [Commits](https://github.com/thoughtbot/factory_bot_rails/compare/v6.5.0...v6.5.1)

---
updated-dependencies:
- dependency-name: factory_bot_rails
  dependency-version: 6.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 08:56:00 +00:00
Daniel O'Connor
389d904d7c Merge pull request #4231 from Growstuff/dependabot/bundler/puma-7.0.2
Bump puma from 7.0.0 to 7.0.2
2025-09-09 18:24:37 +09:30
dependabot[bot]
5bfeb0ce03 Bump puma from 7.0.0 to 7.0.2
Bumps [puma](https://github.com/puma/puma) from 7.0.0 to 7.0.2.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v7.0.0...v7.0.2)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 07:01:07 +00:00
Daniel O'Connor
11b1c84985 Update garden_resource to filter by owner (#4229) 2025-09-09 08:24:15 +09:30
Daniel O'Connor
a21a9e7a09 Update garden_resource to filter by owner 2025-09-08 17:38:03 +09:30
google-labs-jules[bot]
bc11a1b8db Merge pull request #4209 from Growstuff/extend-crop-model
Extend Crop Model and Migrate Data from OpenFarm
2025-09-07 20:03:18 +09:30
Daniel O'Connor
12f6b76dca Merge pull request #4223 from Growstuff/show-history
Render history of activities on gardens, plantings
2025-09-07 19:40:18 +09:30
Daniel O'Connor
dfc75d8916 Merge pull request #4224 from Growstuff/deep-link
Harvests > Unrated Planting > Deep link to content when rating
2025-09-07 18:12:07 +09:30
Daniel O'Connor
798eb1132f Rubocop 2025-09-07 08:10:41 +00:00
Daniel O'Connor
42036a3d3f Fix logic 2025-09-07 08:07:21 +00:00
Daniel O'Connor
47da5f18c9 Merge branch 'dev' into deep-link 2025-09-07 17:35:57 +09:30
Daniel O'Connor
d22555ee42 Deep link to content when rating 2025-09-07 08:04:14 +00:00
Daniel O'Connor
d0f4911bf6 Merge pull request #4221 from Growstuff/CloCkWeRX-patch-3
Update _modal.html.haml to sort consistently
2025-09-07 17:30:17 +09:30
Daniel O'Connor
2bc164bd2e Render history 2025-09-07 07:59:51 +00:00
Daniel O'Connor
6f9fbfa3cd Merge pull request #4220 from Growstuff/CloCkWeRX-patch-2
Fix garden order on planting new
2025-09-07 17:19:05 +09:30
Daniel O'Connor
47d1877568 Merge pull request #4205 from Growstuff/dependabot/github_actions/actions/setup-node-5
Bump actions/setup-node from 4 to 5
2025-09-07 17:18:50 +09:30
Daniel O'Connor
b0555ef89e Update _modal.html.haml to sort consistently 2025-09-07 17:12:14 +09:30
Daniel O'Connor
a5f9edea87 Merge pull request #4217 from Growstuff/skip-to-content
By default, skip to content when linking to garden(s)
2025-09-07 17:09:48 +09:30
Daniel O'Connor
3917f263b8 Fix garden order on planting new 2025-09-07 17:09:27 +09:30
Daniel O'Connor
cfc486ce86 Merge pull request #4219 from Growstuff/dev
release 69.1
2025-09-07 15:07:58 +09:30
Daniel O'Connor
f55f88c4af Merge pull request #4218 from Growstuff/fix-activities
Activities > Due Date > Only render if available
2025-09-07 15:06:20 +09:30
Daniel O'Connor
3725957065 Only render if available 2025-09-07 05:34:49 +00:00
Daniel O'Connor
a900c2eb2f Merge pull request #4185 from Growstuff/dev
Release 69
2025-09-07 14:55:40 +09:30
Daniel O'Connor
8fbc02caf3 Timeline 2025-09-07 05:20:35 +00:00
dependabot[bot]
b2d8530923 Bump actions/setup-node from 4 to 5
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-07 05:19:53 +00:00
Daniel O'Connor
186f07109c Add skip link 2025-09-07 05:17:14 +00:00
Daniel O'Connor
b3c749566b More named anchors 2025-09-07 05:14:18 +00:00
Daniel O'Connor
1eb84b9765 By default, skip to content when linking to garden(s) 2025-09-07 05:11:30 +00:00
Daniel O'Connor
9b1699b061 Merge pull request #4215 from Growstuff/activities-detail
Surface more Activities detail
2025-09-07 14:39:13 +09:30
Daniel O'Connor
06c907742d Merge pull request #4216 from Growstuff/CloCkWeRX-patch-2
Update ci-features-posts.yml
2025-09-07 14:38:54 +09:30
Daniel O'Connor
d8b84e611b Update ci-features-posts.yml 2025-09-07 14:37:18 +09:30
Daniel O'Connor
c0ab0b085e Merge pull request #4214 from Growstuff/fix-cards
Fix styling of cards to space evenly
2025-09-07 14:34:14 +09:30
Daniel O'Connor
99d50a7d4b Merge pull request #4212 from Growstuff/fix-ordering
Change garden sort order to be by name and planting by most to least recent when creating an activity
2025-09-07 14:33:46 +09:30
Daniel O'Connor
84da4c0f4f Fix styling of cards to space evenly 2025-09-07 04:24:22 +00:00
Daniel O'Connor
f650d1b8fa Change garden sort order to be by name and planting by most to lease recent when creating an activity 2025-09-07 03:54:20 +00:00
dependabot[bot]
91ef85da0b Merge pull request #4203 from Growstuff/dependabot/bundler/rubocop-1.80.2 2025-09-05 19:09:32 +00:00
dependabot[bot]
30f799c4b9 Bump rubocop from 1.80.1 to 1.80.2
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.80.1 to 1.80.2.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.80.1...v1.80.2)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.80.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-05 08:10:40 +00:00
dependabot[bot]
9283e64366 Merge pull request #4204 from Growstuff/dependabot/bundler/puma-7.0.0 2025-09-05 08:09:30 +00:00
dependabot[bot]
b0b759ef60 Bump puma from 6.6.1 to 7.0.0
Bumps [puma](https://github.com/puma/puma) from 6.6.1 to 7.0.0.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v6.6.1...v7.0.0)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-05 07:59:48 +00:00
Daniel O'Connor
83cf4117fb Merge pull request #4201 from Growstuff/feature/preserve-screenshots-as-artifacts
CI: Preserve screenshots as build artifacts in all feature workflows
2025-09-05 17:29:15 +09:30
Daniel O'Connor
521e649cac Merge pull request #4202 from Growstuff/dependabot/bundler/pg-1.6.2
Bump pg from 1.6.1 to 1.6.2
2025-09-04 20:46:29 +09:30
dependabot[bot]
1dc587d4b5 Bump pg from 1.6.1 to 1.6.2
Bumps [pg](https://github.com/ged/ruby-pg) from 1.6.1 to 1.6.2.
- [Changelog](https://github.com/ged/ruby-pg/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ged/ruby-pg/compare/v1.6.1...v1.6.2)

---
updated-dependencies:
- dependency-name: pg
  dependency-version: 1.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 00:26:26 +00:00
Daniel O'Connor
77a6e96ece Merge branch 'dev' into feature/preserve-screenshots-as-artifacts 2025-09-04 08:43:44 +09:30
dependabot[bot]
2e89dc0c4f Merge pull request #4200 from Growstuff/dependabot/bundler/rubocop-rspec-3.7.0 2025-09-03 09:49:39 +00:00
dependabot[bot]
dc1b46c7be Bump rubocop-rspec from 3.6.0 to 3.7.0
Bumps [rubocop-rspec](https://github.com/rubocop/rubocop-rspec) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/rubocop/rubocop-rspec/releases)
- [Changelog](https://github.com/rubocop/rubocop-rspec/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-rspec/compare/v3.6.0...v3.7.0)

---
updated-dependencies:
- dependency-name: rubocop-rspec
  dependency-version: 3.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 05:55:09 +00:00
google-labs-jules[bot]
d61227bad0 CI: Preserve screenshots as build artifacts
Adjust the behaviour of capybara-screenshot / GitHub CI to preserve the screenshots as a build artifact.

This change adds a step to all `ci-features-*.yml` workflows to upload the
`tmp/screenshots` directory as a build artifact on failure. This will help with
debugging failing feature tests.
2025-09-02 23:30:25 +00:00
google-labs-jules[bot]
765fab1104 CI: Preserve screenshots as build artifacts
Adjust the behaviour of capybara-screenshot / GitHub CI to preserve the screenshots as a build artifact.

This change adds a step to the `ci-features.yml` workflow to upload the
`tmp/screenshots` directory as a build artifact on failure. This will help with
debugging failing feature tests.
2025-09-02 23:30:02 +00:00
Daniel O'Connor
8000a51e8b Remove JS testing from footer (#4192) 2025-09-02 07:42:09 +09:30
Daniel O'Connor
b3ba05d834 Fix crash on adding Flickr photo (#4198)
* Update photo.rb

* Update photo.rb

* Update app/models/photo.rb

* Update app/models/photo.rb
2025-09-02 02:17:28 +09:30
Daniel O'Connor
110b18cc9e Merge pull request #4197 from Growstuff/CloCkWeRX-patch-4
Update README.md
2025-09-02 00:37:09 +09:30
Daniel O'Connor
396af468fa Merge branch 'dev' into CloCkWeRX-patch-4 2025-09-02 00:36:56 +09:30
Daniel O'Connor
ac2d998711 Update README.md 2025-09-02 00:36:30 +09:30
Daniel O'Connor
4564d0afe0 Merge pull request #4196 from Growstuff/CloCkWeRX-patch-3
Update README.md
2025-09-02 00:34:07 +09:30
Daniel O'Connor
a325ada964 Merge pull request #4194 from Growstuff/CloCkWeRX-patch-2
Rename _facts.haml to _facts.html.haml
2025-09-02 00:33:35 +09:30
Daniel O'Connor
2b818e9f50 Update README.md 2025-09-02 00:33:12 +09:30
Daniel O'Connor
9b9de06140 Update README.md 2025-09-02 00:12:59 +09:30
Daniel O'Connor
2f290efc5b Rename _facts.haml to _facts.html.haml 2025-09-02 00:03:04 +09:30
Daniel O'Connor
23ef0f9cac Merge pull request #4193 from Growstuff/CloCkWeRX-patch-2
Update _facts.haml
2025-09-02 00:02:31 +09:30
Daniel O'Connor
7106b141d9 Update _facts.haml 2025-09-02 00:02:17 +09:30
Daniel O'Connor
ada567dcab Remove JS testing from footer 2025-09-01 14:28:24 +00:00
Daniel O'Connor
d620dc3bfc Merge pull request #4190 from Growstuff/less-js
Specs: Sign up, sign in don't need JS
2025-09-01 23:57:29 +09:30
Daniel O'Connor
c189e3b01a Merge pull request #4062 from Growstuff/feature/planting-rating
Add overall_rating to Plantings
2025-09-01 23:56:13 +09:30
Daniel O'Connor
70e6c44d82 Sign up, sign in don't need JS 2025-09-01 13:20:04 +00:00
Daniel O'Connor
b69d1bd14b Merge pull request #4189 from Growstuff/remove-openfarm-service
Remove openfarm service
2025-09-01 22:34:52 +09:30
Daniel O'Connor
468e34a551 Remove openfarm service 2025-09-01 12:56:22 +00:00
Daniel O'Connor
8385beb406 Merge pull request #4188 from Growstuff/remove-dead-gems
Remove haml-lint-extractor
2025-09-01 21:55:26 +09:30
google-labs-jules[bot]
0f4803392d Add seed source to Seed model (#4186)
* Add seed source to Seed model

* Update _form.html.haml

* Add to schema

* Default option

* Default option

* Fix test

---------

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>
2025-09-01 21:47:31 +09:30
Daniel O'Connor
d185ce495f Remove haml-lint-extractor 2025-09-01 12:13:08 +00:00
Daniel O'Connor
90bd70603b Add a lot of indexes (#4187) 2025-09-01 21:06:58 +09:30
Daniel O'Connor
a4db05c0f6 Add a lot of indexes 2025-09-01 11:25:02 +00:00
google-labs-jules[bot]
0079513b35 Merge pull request #4183 from Growstuff/feature/timeline-likes
Feature: Display likes on timeline
2025-09-01 19:51:24 +09:30
Daniel O'Connor
29543d1d37 Release 68 (#4170)
* Improve menu again

* Fix crop button annoyance

* feat: Add PWA installation instructions to homepage

This commit adds instructions for mobile users on how to install the Growstuff website as a Progressive Web App (PWA).

The changes include:
- A new section on the homepage with instructions for both iOS and Android devices. This section is only visible to logged-out users.
- New translations for the instructions in the `en.yml` locale file.
- Basic styling for the new section.
- Updated feature tests to verify the new section's visibility.

* Restyle slightly

* Styling

* Github lure

* Make links bold, not all of the stats text

* Adjust specs

* Fix width of ready to harvest

* Update spec/features/home/home_spec.rb

* Fix display

* Fix text display wonkyness

* Merge pull request #4173 from Growstuff/translate-confirm

Garden Delete - Extract strings and fix missing translation bug

* Seeds for trade - avoid showing expired seeds on homepage. (#4176)

* Improve date visibility

* Ensure when seeding seeds, it's false

* Typo

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-31 15:23:16 +09:30
Daniel O'Connor
dfb791bf55 Merge pull request #4167 from Growstuff/dev
Release67, take 3
2025-08-30 01:16:01 +09:30
Daniel O'Connor
484797421e Merge pull request #4165 from Growstuff/dev
Release 67, attempt 2
2025-08-29 23:32:29 +09:30
Daniel O'Connor
a366d68c22 Merge pull request #4160 from Growstuff/dev
Release67 - September 2025?
2025-08-29 20:03:05 +09:30
Daniel O'Connor
e7dba3f0e9 Merge pull request #4147 from Growstuff/dev
August 24 Release (Release 66)
2025-08-24 17:02:03 +09:30
132 changed files with 4837 additions and 2787 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -178,7 +178,6 @@ group :development, :test do
gem 'dotenv-rails'
# cli utils
gem 'haml-i18n-extractor', require: false
gem 'haml_lint', '>= 0.25.1', require: false # Checks haml files for goodness
gem 'i18n-tasks', require: false # adds tests for finding missing and unused translations
gem 'rspectre', require: false # finds unused code in specs
@@ -188,18 +187,16 @@ end
group :test do
gem 'axe-core-capybara'
gem 'axe-core-rspec'
gem "percy-capybara", "~> 5.0.0"
gem 'rails-controller-testing'
gem "rspec-rebound"
gem 'selenium-webdriver'
gem 'timecop'
gem 'vcr'
gem "rspec-rebound"
gem "percy-capybara", "~> 5.0.0"
end
group :travis do
gem 'platform-api'
end
gem "i18n_data", "~> 1.1"

View File

@@ -84,7 +84,7 @@ GEM
activesupport (>= 7.1)
active_record_union (1.3.0)
activerecord (>= 4.0)
active_utils (3.5.0)
active_utils (3.6.0)
activesupport (>= 4.2)
i18n
activejob (7.2.2.2)
@@ -142,7 +142,7 @@ GEM
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
bigdecimal (3.2.2)
bigdecimal (3.2.3)
bluecloth (2.2.0)
bonsai-elasticsearch-rails (7.0.1)
elasticsearch-model (< 8)
@@ -198,7 +198,7 @@ GEM
comfy_bootstrap_form (4.0.9)
rails (>= 5.0.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
connection_pool (2.5.4)
crass (1.0.6)
crowdin-api (1.12.0)
open-uri (>= 0.1.0, < 0.2.0)
@@ -208,7 +208,7 @@ GEM
gli (>= 2.7.0)
i18n (>= 0.6.4)
rubyzip (>= 1.0.0)
csv (3.3.1)
csv (3.3.5)
csv_shaper (1.4.0)
activesupport (>= 3.0.0)
csv
@@ -257,9 +257,9 @@ GEM
excon (1.2.5)
logger
execjs (2.10.0)
factory_bot (6.5.4)
factory_bot (6.5.5)
activesupport (>= 6.1.0)
factory_bot_rails (6.5.0)
factory_bot_rails (6.5.1)
factory_bot (~> 6.5)
railties (>= 6.1.0)
faker (3.5.2)
@@ -277,7 +277,7 @@ GEM
friendly_id (5.5.1)
activerecord (>= 4.0.0)
gbifrb (0.2.0)
geocoder (1.8.5)
geocoder (1.8.6)
base64 (>= 0.1.0)
csv (>= 3.0.0)
gibbon (1.2.1)
@@ -294,12 +294,6 @@ GEM
temple (>= 0.8.2)
thor
tilt
haml-i18n-extractor (0.5.9)
activesupport
haml
highline
tilt
trollop (= 1.16.2)
haml-rails (2.1.0)
actionpack (>= 5.1)
activesupport (>= 5.1)
@@ -343,7 +337,7 @@ GEM
terminal-table (>= 1.5.1)
i18n_data (1.1.0)
simple_po_parser (~> 1.1)
icalendar (2.11.2)
icalendar (2.12.0)
base64
ice_cube (~> 0.16)
logger
@@ -362,7 +356,7 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.13.2)
json (2.15.0)
json-schema (5.1.0)
addressable (~> 2.8)
jsonapi-resources (0.10.7)
@@ -463,8 +457,8 @@ GEM
racc
percy-capybara (5.0.0)
capybara (>= 3)
pg (1.6.1)
pg (1.6.1-x86_64-linux)
pg (1.6.2)
pg (1.6.2-x86_64-linux)
platform-api (3.8.0)
heroics (~> 0.1.1)
moneta (~> 1.0.0)
@@ -473,7 +467,7 @@ GEM
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
prism (1.4.0)
prism (1.5.1)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
@@ -481,11 +475,11 @@ GEM
date
stringio
public_suffix (6.0.1)
puma (6.6.1)
puma (7.0.4)
nio4r (~> 2.0)
query_diet (0.7.2)
racc (1.8.1)
rack (2.2.17)
rack (2.2.18)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-protection (3.2.0)
@@ -549,10 +543,10 @@ GEM
rdoc (6.14.2)
erb
psych (>= 4.0.0)
recaptcha (5.20.1)
recaptcha (5.21.1)
redis-client (0.23.2)
connection_pool
regexp_parser (2.11.2)
regexp_parser (2.11.3)
reline (0.6.2)
io-console (~> 0.5)
responders (3.1.1)
@@ -563,7 +557,7 @@ GEM
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rexml (3.4.1)
rexml (3.4.2)
rouge (4.1.2)
rspec (3.13.0)
rspec-core (~> 3.13.0)
@@ -607,7 +601,7 @@ GEM
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rubocop (1.80.1)
rubocop (1.81.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -615,10 +609,10 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.46.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.46.0)
rubocop-ast (1.47.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
@@ -636,7 +630,7 @@ GEM
rubocop-rake (0.7.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
rubocop-rspec (3.6.0)
rubocop-rspec (3.7.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0)
@@ -701,12 +695,11 @@ GEM
tilt (2.6.1)
timecop (0.9.10)
timeout (0.4.3)
trollop (1.16.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.1.5)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.1.0)
unicorn (6.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
@@ -788,7 +781,6 @@ DEPENDENCIES
gibbon (~> 1.2.0)
gravatar-ultimate
haml
haml-i18n-extractor
haml-rails
haml_lint (>= 0.25.1)
hashie (>= 3.5.3)

View File

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

View File

@@ -16,3 +16,27 @@
.homepage--list-item {
height: 100px;
}
.releases {
.card {
.card-header {
}
.card-body {
h2 {
background-color: transparent;
color: black;
box-shadow: none;
}
img {
border: 0.5em solid #111;
margin-left: 5%;
margin-right: 5%;
width: 90%;
}
ul {
margin-bottom: 1em;
margin-top: 1em;
}
}
}
}

View File

@@ -132,6 +132,8 @@ section {
border-radius: 5%;
margin: 0.5em 0.5em 0.5em 0;
width: 200px;
align-items: stretch;
justify-content: space-between;
.img-card {
border-top-left-radius: 5%;

View File

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

View File

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

View File

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

View File

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

View File

@@ -192,6 +192,8 @@ class CropsController < ApplicationController
:parent_id, :perennial,
:request_notes, :reason_for_rejection,
:rejection_notes,
:row_spacing, :spread, :height,
:sowing_method, :sun_requirements, :growing_degree_days,
scientific_names_attributes: %i(scientific_name _destroy id)
)
end

View File

@@ -20,6 +20,7 @@ class GardensController < DataController
def show
@current_plantings = @garden.plantings.current.where.not(failed: true).includes(:crop, :owner).order(planted_at: :desc)
@current_activities = @garden.activities.current.includes(:owner).order(created_at: :desc)
@finished_activities = @garden.activities.finished.includes(:owner).order(created_at: :desc)
@finished_plantings = @garden.plantings.finished.includes(:crop)
@suggested_companions = Crop.approved.where(
id: CropCompanion.where(crop_a_id: @current_plantings.select(:crop_id)).select(:crop_b_id)
@@ -38,7 +39,10 @@ class GardensController < DataController
def create
@garden.owner_id = current_member.id
flash[:notice] = I18n.t('gardens.created') if @garden.save
if @garden.save
link = new_activity_path(name: 'Weed the garden bed', garden_id: @garden.id, due_date: 2.weeks.from_now.to_date)
flash[:notice] = t('gardens.created_prompt_html', link: link).html_safe
end
respond_with(@garden)
end

View File

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

View File

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

View File

@@ -43,6 +43,7 @@ class SeedsController < DataController
def new
@seed = Seed.new
@seed.source = 'my own seed saving'
if params[:planting_slug]
@planting = Planting.find_by(slug: params[:planting_slug])
@@ -56,6 +57,7 @@ class SeedsController < DataController
def create
@seed = Seed.new(seed_params)
@seed.source ||= 'my own seed saving'
@seed.finished ||= false
@seed.owner = current_member
@seed.crop = @seed.parent_planting.crop if @seed.parent_planting
@@ -84,7 +86,7 @@ class SeedsController < DataController
:crop_id, :description, :quantity, :plant_before,
:parent_planting_id, :saved_at,
:days_until_maturity_min, :days_until_maturity_max,
:organic, :gmo,
:organic, :gmo, :source,
:heirloom, :tradable_to, :slug,
:finished, :finished_at
)

View File

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

View File

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

View File

@@ -85,7 +85,20 @@ module ButtonsHelper
end
def activity_edit_button(activity, classes: "btn btn-raised btn-info")
edit_button(edit_activity_path(activity), classes:)
edit_button(edit_activity_path(slug: activity.slug), classes:)
end
def activity_copy_button(activity, classes: 'btn')
link_to new_activity_path(
name: activity.name,
description: activity.description,
category: activity.category,
garden_id: activity.garden_id,
planting_id: activity.planting_id,
due_date: activity.due_date
), class: classes do
copy_icon + ' ' + t('buttons.copy')
end
end
def activity_finish_button(activity, classes: 'btn btn-default btn-secondary')

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,14 +8,6 @@ module OpenFarmData
fetch_attr('main_image_path')
end
def height
fetch_attr('height')
end
def spread
fetch_attr('spread')
end
def svg_icon
icon = fetch_attr('svg_icon')
return icon if icon.present?
@@ -31,10 +23,6 @@ module OpenFarmData
fetch_attr('description')
end
def row_spacing
fetch_attr('row_spacing')
end
def common_names
fetch_attr('common_names')
end
@@ -43,22 +31,10 @@ module OpenFarmData
fetch_attr('binomial_name')
end
def sowing_method
fetch_attr('sowing_method')
end
def main_image_path
fetch_attr('main_image_path')
end
def sun_requirements
fetch_attr('sun_requirements')
end
def growing_degree_days
fetch_attr('growing_degree_days')
end
def processing_pictures
fetch_attr('processing_pictures')
end
@@ -67,6 +43,6 @@ module OpenFarmData
def fetch_attr(key)
return if openfarm_data.blank?
openfarm_data.fetch('attributes', {}).fetch(key, nil)
openfarm_data.dig('attributes', key)
end
end

View File

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

View File

@@ -90,7 +90,7 @@ class Crop < ApplicationRecord
def popular_plant_parts
PlantPart.joins(:harvests)
.where("crop_id = ?", id)
.order("count_harvests_id DESC")
.order(count_harvests_id: :desc)
.group("plant_parts.id", "plant_parts.name")
.count("harvests.id")
end
@@ -153,6 +153,12 @@ class Crop < ApplicationRecord
where(["lower(crops.name) = :value", { value: name.downcase }])
end
def all_companions
return companions unless parent
(companions + parent.companions).uniq
end
private
def count_uses_of_property(col_name)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,6 +41,14 @@
= f.radio_button(:perennial, true, label: "Perennial")
%span.help-block Living more than two years
%h2 OpenFarm Data
= f.number_field :row_spacing, label: 'Row Spacing (cm)', min: 0
= f.number_field :spread, label: 'Spread (cm)', min: 0
= f.number_field :height, label: 'Height (cm)', min: 0
= f.text_field :sowing_method
= f.text_field :sun_requirements
= f.number_field :growing_degree_days, min: 0
- unless @crop.approved?
= link_to 'Search wikipedia', "https://en.wikipedia.org/w/index.php?search=#{@crop.name}", target: '_blank'
= f.url_field :en_wikipedia_url, id: "en_wikipedia_url", label: 'Wikipedia URL'

View File

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

View File

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

View File

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

View File

@@ -24,10 +24,10 @@
%section.prediction
= cute_icon
= render 'predictions', crop: @crop
- if @crop.companions.any?
- if @crop.all_companions.any?
%section.companions
%h2 Companions
- @crop.companions.each do |companion|
- @crop.all_companions.each do |companion|
= render 'crops/tiny', crop: companion
%section.photos
@@ -111,6 +111,8 @@
= render 'harvests', crop: @crop
= render 'find_seeds', crop: @crop
= render 'openfarm_data', crop: @crop
= cute_icon
.card
.card-body

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,11 +11,11 @@
%br
%p
- if current_member.plantings.active.any?
= link_to member_path(current_member, anchor: "#content"), class: 'btn btn-dark' do
= link_to member_path(current_member, anchor: "content"), class: 'btn btn-dark' do
= planting_icon
Track my plantings
%p
= link_to member_gardens_path(current_member), class: 'btn btn-dark' do
= link_to member_gardens_path(current_member, anchor: "content"), class: 'btn btn-dark' do
= garden_icon
Show me my garden
- else
@@ -62,6 +62,7 @@
%section.members
= cute_icon
= render 'members', cached: true
.row
.col-12.col-lg-6
%section.pwa-install
= cute_icon
@@ -74,4 +75,6 @@
.card
.card-body
%h3= t('home.pwa_android_title')
%p= t('home.pwa_android_steps_html')
%p= t('home.pwa_android_steps_html')
.col-12.col-lg-6
= render 'releases'

View File

@@ -2,10 +2,10 @@
%ul.navbar-nav.mr-auto.bg-dark
- if signed_in?
%li.nav-item
= link_to timeline_index_path, method: :get, class: 'nav-link text-white' do
= link_to timeline_index_path, method: :get, class: 'nav-link text-white', title: "Timeline" do
= 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', title: "My gardens" do
= link_to member_gardens_path(current_member, anchor: "content"), class: 'nav-link text-white', title: "My gardens" do
= image_icon 'gardens'
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}

View File

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

View File

@@ -83,14 +83,14 @@
.row
%section.order-3.order-md-1.col-12= render "map", member: @member
- if @harvesting.size.positive?
%section.harvests.order-2.order-md-1.col-12
%section.harvests.order-2.order-md-1.col-12#harvests
%h2 Ready to harvest
.index-cards
- @harvesting.each do |planting|
= render 'plantings/thumbnail', planting: planting
- if @others.size.positive?
%section.planting-progress.order-2.order-md-1.col-12
%section.planting-progress.order-2.order-md-1.col-12#planting-progress
%h2 Progress report
%p Still growing and not ready for harvesting.
.list-group
@@ -99,7 +99,7 @@
%span= render 'plantings/tiny', planting: planting
%span= render 'plantings/progress', planting: planting
- if @late.size.positive?
%section.late.order-2.order-md-1.col-12
%section.late.order-2.order-md-1.col-12#late
%h2 Late
%p
These plantings are at the end of their lifecycle.
@@ -109,7 +109,7 @@
- @late.each do |planting|
= render 'plantings/thumbnail', planting: planting
- if @super_late.any?
%section.superlate.order-2.order-md-1.col-12
%section.superlate.order-2.order-md-1.col-12#superlate
%h2 Super late
%p
We suspect the following plantings finished long ago and no longer need tracking.
@@ -122,14 +122,14 @@
planted on #{planting.planted_at.to_date}
- if @harvests.any?
%section.havests.order-2.order-md-1.col-12
%section.havests.order-2.order-md-1.col-12#recent-harvests
%h2 Recent Harvests
.index-cards
- @harvests.each do |harvest|
= render 'harvests/thumbnail', harvest: harvest
- if @activity.any?
%section.activity.order-2.order-md-1.col-12
%section.activity.order-2.order-md-1.col-12#activity
%h2 Activity
.list-group
- @activity.each do |event|

View File

@@ -89,3 +89,9 @@
- if planting.finished_at.present?
%span.plantingfact--finish
= planting.finished_at.year
- if planting.overall_rating.present?
.card.fact-card
.card-body
%h3 Overall Rating
%p.card-text
%strong= "#{planting.overall_rating}/5"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ Mailboxer.setup do |config|
config.uses_emails = true
# Configures the default from for emails sent for Messages and Notifications
config.default_from = "Growstuff <#{ENV.fetch('GROWSTUFF_EMAIL', "no-reply@growstuff.org")}>"
config.default_from = "Growstuff <#{ENV.fetch('GROWSTUFF_EMAIL', 'no-reply@growstuff.org')}>"
# Configures the methods needed by mailboxer
# config.email_method = :email

View File

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

View File

@@ -72,6 +72,7 @@ en:
add: Add
add_photo: Add photo
add_seed_to_stash: Add %{crop_name} seeds to stash
copy: Copy
delete: Delete
edit: Edit
harvest: Harvest
@@ -125,6 +126,7 @@ en:
updated: Garden was successfully updated.
confirm_delete: All plantings associated with this garden will also be deleted. Are you sure?
confirm_deactivate: All plantings associated with this garden will be marked as finished. Are you sure?
created_prompt_html: "Garden was successfully created. Would you like to <a href=\"%{link}\">plan to weed this garden bed in two weeks</a>?"
harvests:
created: Harvest was successfully created.
harvest_something: Harvest something

View File

@@ -16,6 +16,7 @@ Rails.application.routes.draw do
}
devise_scope :member do
get '/members/unsubscribe/:message' => 'members#unsubscribe', as: 'unsubscribe_member'
post '/members/regenerate_api_token' => 'registrations#regenerate_api_token', as: 'regenerate_api_token'
end
match '/members/:id/finish_signup' => 'members#finish_signup', via: %i(get patch), as: :finish_signup
@@ -137,10 +138,14 @@ Rails.application.routes.draw do
namespace :admin do
resources :members, param: :slug
resources :roles
resources :crops, param: :slug do
resources :crop_companions
end
end
namespace :api do
namespace :v1 do
jsonapi_resources :activities
jsonapi_resources :crops
jsonapi_resources :gardens
jsonapi_resources :harvests

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddSourceUrlToCropCompanions < ActiveRecord::Migration[6.1]
def change
add_column :crop_companions, :source_url, :string
end
end

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ class CreateGardenCollaborators < ActiveRecord::Migration[7.2]
t.references :member
t.references :garden
t.timestamps
t.index [:member_id, :garden_id], unique: true
t.index %i(member_id garden_id), unique: true
end
end
end

View File

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

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