mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-05-26 01:37:39 -04:00
Compare commits
40 Commits
mainline
...
feature/we
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2930362278 | ||
|
|
8a8fd6eabd | ||
|
|
fe9fdd9147 | ||
|
|
ee7b9ab39f | ||
|
|
6aadb4d805 | ||
|
|
a42682a59e | ||
|
|
6294c54139 | ||
|
|
c168e8e4c9 | ||
|
|
6ac438a07f | ||
|
|
2380c662fe | ||
|
|
4589839c64 | ||
|
|
1f6f3c4dfd | ||
|
|
5a7f41537f | ||
|
|
1281795c97 | ||
|
|
c219d447cc | ||
|
|
1e3f86a349 | ||
|
|
680afe02cc | ||
|
|
914cfe99c8 | ||
|
|
4643fbd92e | ||
|
|
5ac709ffd1 | ||
|
|
9833801a42 | ||
|
|
4d1e8aede6 | ||
|
|
24f41350a9 | ||
|
|
503ba716bb | ||
|
|
e423e6ac79 | ||
|
|
e63089e03b | ||
|
|
6ce347af82 | ||
|
|
64af597dec | ||
|
|
7160f50ac1 | ||
|
|
67793e7d8d | ||
|
|
7b677d6b2c | ||
|
|
2b6de6d2ba | ||
|
|
5d133b0f58 | ||
|
|
6b8d7686d6 | ||
|
|
103e1171c6 | ||
|
|
c9a0e2259f | ||
|
|
51b8c2bfe9 | ||
|
|
e599b9872a | ||
|
|
4883d6b0e0 | ||
|
|
d828fd5c35 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ko_fi: jennyscottthompson
|
||||
@@ -1,25 +1,31 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2026-04-25 16:44:38 UTC using RuboCop version 1.86.1.
|
||||
# on 2026-05-02 06:53:56 UTC using RuboCop version 1.86.1.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 19
|
||||
Capybara/NegationMatcherAfterVisit:
|
||||
# Offense count: 407
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Capybara/RSpec/HaveContent:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 21
|
||||
Capybara/RSpec/NegationMatcherAfterVisit:
|
||||
Exclude:
|
||||
- 'spec/features/admin/reverting_crops_spec.rb'
|
||||
- 'spec/features/crops/crop_detail_page_spec.rb'
|
||||
- 'spec/features/crops/crop_wranglers_spec.rb'
|
||||
- 'spec/features/gardens/gardens_spec.rb'
|
||||
- 'spec/features/members/blocking_spec.rb'
|
||||
- 'spec/features/members/deletion_spec.rb'
|
||||
- 'spec/features/members/following_spec.rb'
|
||||
- 'spec/features/members/profile_spec.rb'
|
||||
- 'spec/features/plantings/planting_a_crop_spec.rb'
|
||||
|
||||
# Offense count: 14
|
||||
Capybara/SpecificMatcher:
|
||||
Capybara/RSpec/SpecificMatcher:
|
||||
Exclude:
|
||||
- 'spec/features/footer_spec.rb'
|
||||
- 'spec/features/gardens/adding_gardens_spec.rb'
|
||||
@@ -28,7 +34,7 @@ Capybara/SpecificMatcher:
|
||||
- 'spec/features/seeds/adding_seeds_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Capybara/VisibilityMatcher:
|
||||
Capybara/RSpec/VisibilityMatcher:
|
||||
Exclude:
|
||||
- 'spec/features/shared_examples/crop_suggest.rb'
|
||||
|
||||
@@ -63,7 +69,13 @@ FactoryBot/ExcessiveCreateList:
|
||||
- 'spec/features/crops/show_spec.rb'
|
||||
- 'spec/features/percy/percy_spec.rb'
|
||||
|
||||
# Offense count: 312
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Layout/EmptyLines:
|
||||
Exclude:
|
||||
- 'config/environments/production.rb'
|
||||
|
||||
# Offense count: 311
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
|
||||
# SupportedHashRocketStyles: key, separator, table
|
||||
@@ -81,7 +93,7 @@ Layout/HashAlignment:
|
||||
- 'spec/requests/api/v1/activities_request_spec.rb'
|
||||
- 'spec/requests/api/v1/members_request_spec.rb'
|
||||
|
||||
# Offense count: 5
|
||||
# Offense count: 8
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
||||
# URISchemes: http, https
|
||||
@@ -89,9 +101,20 @@ Layout/LineLength:
|
||||
Exclude:
|
||||
- 'Gemfile'
|
||||
- 'app/controllers/admin/versions_controller.rb'
|
||||
- 'app/controllers/crops_controller.rb'
|
||||
- 'app/models/concerns/predict_planting.rb'
|
||||
- 'app/models/crop.rb'
|
||||
- 'db/seeds.rb'
|
||||
- 'lib/tasks/members.rake'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented, indented_relative_to_receiver
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
Exclude:
|
||||
- 'app/models/activity.rb'
|
||||
- 'app/models/concerns/predict_harvest.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
@@ -99,23 +122,15 @@ Lint/AmbiguousOperatorPrecedence:
|
||||
Exclude:
|
||||
- 'app/controllers/activities_controller.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Offense count: 3
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: RequireParenthesesForMethodChains.
|
||||
Lint/AmbiguousRange:
|
||||
Exclude:
|
||||
- 'app/models/concerns/search_activities.rb'
|
||||
- 'app/models/concerns/search_harvests.rb'
|
||||
- 'app/models/concerns/search_plantings.rb'
|
||||
- 'db/seeds.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: AllowSafeAssignment.
|
||||
Lint/AssignmentInCondition:
|
||||
Exclude:
|
||||
- 'app/helpers/crops_helper.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowedMethods.
|
||||
# AllowedMethods: enums
|
||||
@@ -158,7 +173,7 @@ Lint/UselessConstantScoping:
|
||||
Exclude:
|
||||
- 'app/controllers/members_controller.rb'
|
||||
|
||||
# Offense count: 61
|
||||
# Offense count: 65
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
||||
Metrics/AbcSize:
|
||||
Max: 295
|
||||
@@ -180,12 +195,12 @@ Metrics/CollectionLiteralLength:
|
||||
Exclude:
|
||||
- 'lib/tasks/import.rake'
|
||||
|
||||
# Offense count: 10
|
||||
# Offense count: 11
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 32
|
||||
|
||||
# Offense count: 82
|
||||
# Offense count: 83
|
||||
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
||||
Metrics/MethodLength:
|
||||
Max: 296
|
||||
@@ -195,7 +210,7 @@ Metrics/MethodLength:
|
||||
Metrics/ModuleLength:
|
||||
Max: 144
|
||||
|
||||
# Offense count: 8
|
||||
# Offense count: 10
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 32
|
||||
@@ -209,6 +224,16 @@ Naming/PredicateMethod:
|
||||
- 'app/models/concerns/finishable.rb'
|
||||
- 'app/models/seed.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
|
||||
# NamePrefix: is_, has_, have_, does_
|
||||
# ForbiddenPrefixes: is_, has_, have_, does_
|
||||
# AllowedMethods: is_a?
|
||||
# MethodDefinitionMacros: define_method, define_singleton_method
|
||||
Naming/PredicatePrefix:
|
||||
Exclude:
|
||||
- 'app/models/member.rb'
|
||||
|
||||
# Offense count: 3
|
||||
RSpec/AnyInstance:
|
||||
Exclude:
|
||||
@@ -221,34 +246,54 @@ RSpec/BeEq:
|
||||
Exclude:
|
||||
- 'spec/requests/api/v1/activities_request_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Offense count: 2
|
||||
RSpec/BeforeAfterAll:
|
||||
Exclude:
|
||||
- 'spec/tasks/import_spec.rb'
|
||||
- 'spec/tasks/members_spec.rb'
|
||||
|
||||
# Offense count: 298
|
||||
# Offense count: 311
|
||||
# Configuration parameters: Prefixes, AllowedPatterns.
|
||||
# Prefixes: when, with, without
|
||||
RSpec/ContextWording:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 36
|
||||
# Offense count: 2
|
||||
# Configuration parameters: IgnoredMetadata.
|
||||
RSpec/DescribeClass:
|
||||
Exclude:
|
||||
- 'spec/models/harvest_prediction_spec.rb'
|
||||
- 'spec/tasks/members_spec.rb'
|
||||
|
||||
# Offense count: 37
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants.
|
||||
# SupportedStyles: described_class, explicit
|
||||
RSpec/DescribedClass:
|
||||
Exclude:
|
||||
- 'spec/mailers/harvest_reminder_mailer_spec.rb'
|
||||
- 'spec/models/like_spec.rb'
|
||||
- 'spec/models/member_spec.rb'
|
||||
- 'spec/services/timeline_service_spec.rb'
|
||||
|
||||
# Offense count: 146
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowConsecutiveOneLiners.
|
||||
RSpec/EmptyLineAfterExample:
|
||||
Exclude:
|
||||
- 'spec/controllers/crops_controller_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
RSpec/EmptyLineAfterFinalLet:
|
||||
Exclude:
|
||||
- 'spec/controllers/crops_controller_spec.rb'
|
||||
|
||||
# Offense count: 161
|
||||
# Configuration parameters: CountAsOne.
|
||||
RSpec/ExampleLength:
|
||||
Max: 27
|
||||
|
||||
# Offense count: 32
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
@@ -283,27 +328,18 @@ RSpec/IncludeExamples:
|
||||
- 'spec/views/photos/show.html.haml_spec.rb'
|
||||
- 'spec/views/seeds/index.rss.haml_spec.rb'
|
||||
|
||||
# Offense count: 37
|
||||
# Offense count: 2
|
||||
# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.
|
||||
RSpec/IndexedLet:
|
||||
Exclude:
|
||||
- 'spec/controllers/harvests_controller_spec.rb'
|
||||
- 'spec/controllers/plantings_controller_spec.rb'
|
||||
- 'spec/features/crops/crop_photos_spec.rb'
|
||||
- 'spec/features/members/list_spec.rb'
|
||||
- 'spec/features/members/profile_spec.rb'
|
||||
- 'spec/features/percy/percy_spec.rb'
|
||||
- 'spec/features/planting_reminder_spec.rb'
|
||||
- 'spec/features/timeline/index_spec.rb'
|
||||
- 'spec/models/member_spec.rb'
|
||||
- 'spec/views/forums/index.html.haml_spec.rb'
|
||||
- 'spec/models/activity_spec.rb'
|
||||
|
||||
# Offense count: 719
|
||||
# Offense count: 711
|
||||
# Configuration parameters: AssignmentOnly.
|
||||
RSpec/InstanceVariable:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 41
|
||||
# Offense count: 43
|
||||
RSpec/LetSetup:
|
||||
Enabled: false
|
||||
|
||||
@@ -318,7 +354,7 @@ RSpec/MessageChain:
|
||||
Exclude:
|
||||
- 'spec/models/member_spec.rb'
|
||||
|
||||
# Offense count: 23
|
||||
# Offense count: 65
|
||||
# Configuration parameters: .
|
||||
# SupportedStyles: have_received, receive
|
||||
RSpec/MessageSpies:
|
||||
@@ -329,11 +365,11 @@ RSpec/MultipleDescribes:
|
||||
Exclude:
|
||||
- 'spec/features/crops/crop_wranglers_spec.rb'
|
||||
|
||||
# Offense count: 191
|
||||
# Offense count: 235
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 19
|
||||
|
||||
# Offense count: 166
|
||||
# Offense count: 171
|
||||
# Configuration parameters: AllowSubject.
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 16
|
||||
@@ -344,24 +380,35 @@ RSpec/MultipleMemoizedHelpers:
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 109
|
||||
# Offense count: 112
|
||||
# Configuration parameters: AllowedGroups.
|
||||
RSpec/NestedGroups:
|
||||
Max: 6
|
||||
|
||||
# Offense count: 407
|
||||
# Offense count: 366
|
||||
# Configuration parameters: AllowedPatterns.
|
||||
# AllowedPatterns: ^expect_, ^assert_
|
||||
RSpec/NoExpectationExample:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Offense count: 9
|
||||
RSpec/PendingWithoutReason:
|
||||
Exclude:
|
||||
- 'spec/features/members/blocking_spec.rb'
|
||||
- 'spec/features/plantings/planting_a_crop_spec.rb'
|
||||
- 'spec/features/seeds/misc_seeds_spec.rb'
|
||||
- 'spec/features/unsubscribing_spec.rb'
|
||||
- 'spec/models/ability_spec.rb'
|
||||
- 'spec/requests/api/v1/gardens_request_spec.rb'
|
||||
|
||||
# Offense count: 5
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers.
|
||||
# SupportedStyles: inflected, explicit
|
||||
RSpec/PredicateMatcher:
|
||||
Exclude:
|
||||
- 'spec/tasks/members_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
RSpec/RepeatedDescription:
|
||||
Exclude:
|
||||
@@ -386,18 +433,18 @@ RSpec/ScatteredSetup:
|
||||
- 'spec/features/percy/percy_spec.rb'
|
||||
- 'spec/features/plantings/prediction_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Offense count: 2
|
||||
# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
|
||||
# SupportedInflectors: default, active_support
|
||||
RSpec/SpecFilePathFormat:
|
||||
Exclude:
|
||||
- 'spec/controllers/member_controller_spec.rb'
|
||||
- 'spec/mailers/harvest_reminder_mailer_spec.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# Offense count: 2
|
||||
RSpec/StubbedMock:
|
||||
Exclude:
|
||||
- 'spec/controllers/garden_types_controller_spec.rb'
|
||||
- 'spec/controllers/gardens_controller_spec.rb'
|
||||
- 'spec/controllers/photos_controller_spec.rb'
|
||||
- 'spec/models/member_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
@@ -414,6 +461,13 @@ RSpec/VerifiedDoubles:
|
||||
- 'spec/controllers/gardens_controller_spec.rb'
|
||||
- 'spec/views/devise/shared/_links_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: Inferences.
|
||||
RSpecRails/InferredSpecType:
|
||||
Exclude:
|
||||
- 'spec/mailers/harvest_reminder_mailer_spec.rb'
|
||||
|
||||
# Offense count: 30
|
||||
# Configuration parameters: Database.
|
||||
# SupportedDatabases: mysql, postgresql
|
||||
@@ -470,11 +524,25 @@ Rails/HasManyOrHasOneDependent:
|
||||
- 'app/models/crop.rb'
|
||||
- 'app/models/member.rb'
|
||||
|
||||
# Offense count: 7
|
||||
Rails/HelperInstanceVariable:
|
||||
Exclude:
|
||||
- 'app/helpers/crops_helper.rb'
|
||||
- 'app/helpers/plantings_helper.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Rails/I18nLocaleAssignment:
|
||||
Exclude:
|
||||
- 'spec/features/locale_spec.rb'
|
||||
|
||||
# Offense count: 5
|
||||
Rails/I18nLocaleTexts:
|
||||
Exclude:
|
||||
- 'app/controllers/blocks_controller.rb'
|
||||
- 'app/controllers/comments_controller.rb'
|
||||
- 'app/controllers/messages_controller.rb'
|
||||
- 'config/initializers/comfortable_mexican_sofa.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: IgnoreScopes.
|
||||
Rails/InverseOf:
|
||||
@@ -488,6 +556,12 @@ Rails/LexicallyScopedActionFilter:
|
||||
- 'app/controllers/data_controller.rb'
|
||||
- 'app/controllers/registrations_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/OrderArguments:
|
||||
Exclude:
|
||||
- 'app/models/activity.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Rails/OutputSafety:
|
||||
Exclude:
|
||||
@@ -614,7 +688,7 @@ Rake/MethodDefinitionInTask:
|
||||
Exclude:
|
||||
- 'lib/tasks/growstuff.rake'
|
||||
|
||||
# Offense count: 4
|
||||
# Offense count: 5
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules.
|
||||
# SupportedStyles: nested, compact
|
||||
@@ -623,10 +697,17 @@ Rake/MethodDefinitionInTask:
|
||||
Style/ClassAndModuleChildren:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/crops_controller.rb'
|
||||
- 'config/initializers/rack_attack.rb'
|
||||
- 'lib/actions/oauth_signup_action.rb'
|
||||
- 'lib/haml/filters/escaped_markdown.rb'
|
||||
- 'lib/haml/filters/growstuff_markdown.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/CollectionQuerying:
|
||||
Exclude:
|
||||
- 'app/models/member.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/CommentedKeyword:
|
||||
@@ -657,12 +738,13 @@ Style/FloatDivision:
|
||||
Exclude:
|
||||
- 'app/models/concerns/predict_planting.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Offense count: 2
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: always, always_true, never
|
||||
Style/FrozenStringLiteralComment:
|
||||
Exclude:
|
||||
- 'db/migrate/20260429132911_add_send_harvest_reminder_to_members.rb'
|
||||
- 'spec/lib/haml/filters/growstuff_markdown_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
@@ -678,11 +760,14 @@ Style/IdenticalConditionalBranches:
|
||||
Exclude:
|
||||
- 'lib/actions/oauth_signup_action.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/MapIntoArray:
|
||||
# Offense count: 4
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Style/IfUnlessModifier:
|
||||
Exclude:
|
||||
- 'app/helpers/crops_helper.rb'
|
||||
- 'app/models/concerns/predict_planting.rb'
|
||||
- 'config/initializers/rack_attack.rb'
|
||||
- 'lib/tasks/growstuff.rake'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
@@ -736,6 +821,13 @@ Style/RedundantArgument:
|
||||
Exclude:
|
||||
- 'app/helpers/application_helper.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Style/RedundantBegin:
|
||||
Exclude:
|
||||
- 'app/models/concerns/predict_harvest.rb'
|
||||
- 'app/models/harvest.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: SafeForConstants.
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.4.8
|
||||
3.4.9
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ruby:3.4.8-trixie
|
||||
FROM ruby:3.4.9-trixie
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update -qq && \
|
||||
|
||||
4
Gemfile
4
Gemfile
@@ -130,6 +130,10 @@ gem 'rack-cors'
|
||||
|
||||
gem 'icalendar'
|
||||
|
||||
# for web push notifications
|
||||
gem 'web-push'
|
||||
gem 'serviceworker-rails'
|
||||
|
||||
# for signups as requested by email service
|
||||
gem 'recaptcha'
|
||||
|
||||
|
||||
32
Gemfile.lock
32
Gemfile.lock
@@ -140,15 +140,15 @@ GEM
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
axe-core-api (4.11.2)
|
||||
axe-core-api (4.11.3)
|
||||
dumb_delegator
|
||||
ostruct
|
||||
virtus
|
||||
axe-core-capybara (4.11.2)
|
||||
axe-core-api (= 4.11.2)
|
||||
axe-core-capybara (4.11.3)
|
||||
axe-core-api (= 4.11.3)
|
||||
dumb_delegator
|
||||
axe-core-rspec (4.11.2)
|
||||
axe-core-api (= 4.11.2)
|
||||
axe-core-rspec (4.11.3)
|
||||
axe-core-api (= 4.11.3)
|
||||
dumb_delegator
|
||||
ostruct
|
||||
virtus
|
||||
@@ -276,7 +276,7 @@ GEM
|
||||
elasticsearch-transport (7.0.0)
|
||||
faraday
|
||||
multi_json
|
||||
erb (6.0.2)
|
||||
erb (6.0.4)
|
||||
erubi (1.13.1)
|
||||
execjs (2.10.0)
|
||||
factory_bot (6.5.5)
|
||||
@@ -376,7 +376,7 @@ GEM
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.19.3)
|
||||
json (2.19.4)
|
||||
json-schema (6.2.0)
|
||||
addressable (~> 2.8)
|
||||
bigdecimal (>= 3.1, < 5)
|
||||
@@ -385,6 +385,8 @@ GEM
|
||||
concurrent-ruby
|
||||
railties (>= 4.1)
|
||||
jsonapi-swagger (0.8.1)
|
||||
jwt (3.1.2)
|
||||
base64
|
||||
kgio (2.11.4)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
@@ -472,12 +474,13 @@ GEM
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
open-uri (0.1.0)
|
||||
openssl (3.3.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.3)
|
||||
paper_trail (17.0.0)
|
||||
activerecord (>= 7.1)
|
||||
request_store (~> 1.4)
|
||||
parallel (2.0.1)
|
||||
parallel (2.1.0)
|
||||
parser (3.3.11.1)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
@@ -644,9 +647,9 @@ GEM
|
||||
rubocop-ast (1.49.1)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.7)
|
||||
rubocop-capybara (2.22.1)
|
||||
rubocop-capybara (2.23.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop (~> 1.81)
|
||||
rubocop-factory_bot (2.28.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
@@ -696,6 +699,8 @@ GEM
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 4.0)
|
||||
websocket (~> 1.0)
|
||||
serviceworker-rails (0.6.0)
|
||||
railties (>= 3.1)
|
||||
sidekiq (7.3.10)
|
||||
base64
|
||||
connection_pool (>= 2.3.0, < 3)
|
||||
@@ -748,6 +753,9 @@ GEM
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-push (3.0.2)
|
||||
jwt (~> 3.0)
|
||||
openssl (~> 3.0)
|
||||
webrat (0.7.3)
|
||||
nokogiri (>= 1.2.0)
|
||||
rack (>= 1.0)
|
||||
@@ -872,6 +880,7 @@ DEPENDENCIES
|
||||
scout_apm
|
||||
searchkick
|
||||
selenium-webdriver
|
||||
serviceworker-rails
|
||||
sidekiq
|
||||
sitemap_generator
|
||||
sprockets (< 4)
|
||||
@@ -880,13 +889,14 @@ DEPENDENCIES
|
||||
unicorn
|
||||
validate_url
|
||||
vcr
|
||||
web-push
|
||||
webrat
|
||||
will_paginate
|
||||
will_paginate-bootstrap-style
|
||||
xmlrpc
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.4.8p72
|
||||
ruby 3.4.9
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.22
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// = link_tree ../images
|
||||
// = link serviceworker.js
|
||||
// = link_directory ../javascripts .js
|
||||
// = link_directory ../stylesheets .css
|
||||
|
||||
59
app/assets/javascripts/push_notifications.js
Normal file
59
app/assets/javascripts/push_notifications.js
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||
// listed below.
|
||||
//
|
||||
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
|
||||
// vendor/assets/javascripts directory can be referenced here using a relative path.
|
||||
//
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
||||
//
|
||||
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
||||
// about supported directives.
|
||||
//
|
||||
//= require rails-ujs
|
||||
//= require activestorage
|
||||
//= require_tree .
|
||||
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const pushButton = document.getElementById('enable-push-notifications');
|
||||
if (pushButton) {
|
||||
pushButton.addEventListener('click', () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
const vapidPublicKey = document.querySelector('meta[name="vapid-public-key"]').content;
|
||||
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
|
||||
|
||||
registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: convertedVapidKey
|
||||
}).then(subscription => {
|
||||
fetch('/push_subscriptions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({ subscription: subscription.toJSON() })
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
13
app/assets/javascripts/serviceworker.js.erb
Normal file
13
app/assets/javascripts/serviceworker.js.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
self.addEventListener('push', function(event) {
|
||||
const data = event.data.json();
|
||||
const title = data.title || 'Growstuff';
|
||||
const options = {
|
||||
body: data.body,
|
||||
icon: '/assets/growstuff-apple-touch-icon-precomposed.png',
|
||||
badge: '/assets/growstuff-apple-touch-icon-precomposed.png'
|
||||
};
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(title, options)
|
||||
);
|
||||
});
|
||||
@@ -4,21 +4,16 @@ class ActivitiesController < DataController
|
||||
def index
|
||||
@show_all = params[:all] == '1'
|
||||
|
||||
where = {}
|
||||
where['active'] = true unless @show_all
|
||||
@activities = Activity.includes(:owner).order(created_at: :desc)
|
||||
@activities = @activities.active unless @show_all
|
||||
|
||||
if params[:member_slug].present?
|
||||
@owner = Member.find_by!(slug: params[:member_slug])
|
||||
where['owner_id'] = @owner.id
|
||||
@activities = @activities.where(owner_id: @owner.id)
|
||||
end
|
||||
|
||||
@activities = Activity.search(
|
||||
where:,
|
||||
page: params[:page],
|
||||
limit: 30,
|
||||
boost_by: [:created_at],
|
||||
load: false
|
||||
)
|
||||
@activities = @activities.paginate(page: params[:page], per_page: 30)
|
||||
|
||||
@filename = "Growstuff-#{specifics}Activities-#{Time.zone.now.to_fs(:number)}.csv"
|
||||
respond_with(@activities)
|
||||
end
|
||||
|
||||
@@ -68,7 +68,7 @@ class ApplicationController < ActionController::Base
|
||||
# profile stuff
|
||||
:bio, :location, :latitude, :longitude,
|
||||
# email settings
|
||||
:show_email, :newsletter, :send_notification_email, :send_planting_reminder)
|
||||
:show_email, :newsletter, :send_notification_email, :send_planting_reminder, :send_harvest_reminder)
|
||||
end
|
||||
|
||||
devise_parameter_sanitizer.permit(:account_update) do |member|
|
||||
@@ -80,7 +80,7 @@ class ApplicationController < ActionController::Base
|
||||
:bio, :location, :latitude, :longitude,
|
||||
:website_url, :instagram_handle, :facebook_handle, :bluesky_handle, :other_url,
|
||||
# email settings
|
||||
:show_email, :newsletter, :send_notification_email, :send_planting_reminder,
|
||||
:show_email, :newsletter, :send_notification_email, :send_planting_reminder, :send_harvest_reminder,
|
||||
# update password
|
||||
:current_password)
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ class CropsController < ApplicationController
|
||||
@crops = Crop.search('*', boost_by: %i(plantings_count harvests_count),
|
||||
limit: 100,
|
||||
page: params[:page],
|
||||
load: false)
|
||||
load: (request.format.csv? || request.format.rss? ? { include: %i(parent scientific_names seeds harvests creator plantings) } : false))
|
||||
@num_requested_crops = requested_crops.size if current_member
|
||||
@filename = filename
|
||||
respond_with @crops
|
||||
|
||||
@@ -23,7 +23,7 @@ class HarvestsController < DataController
|
||||
@harvests = Harvest.search('*', where:,
|
||||
limit: 100,
|
||||
page: params[:page],
|
||||
load: false,
|
||||
load: (request.format.csv? ? { include: %i(crop owner plant_part) } : false),
|
||||
boost_by: [:created_at])
|
||||
|
||||
@filename = csv_filename
|
||||
|
||||
@@ -90,11 +90,12 @@ class MembersController < ApplicationController
|
||||
|
||||
EMAIL_TYPE_STRING = {
|
||||
send_notification_email: "direct message notifications",
|
||||
send_planting_reminder: "planting reminders"
|
||||
send_planting_reminder: "planting reminders",
|
||||
send_harvest_reminder: "harvest reminders"
|
||||
}.freeze
|
||||
|
||||
def member_params
|
||||
params.require(:member).permit(:login_name, :tos_agreement, :email, :newsletter)
|
||||
params.require(:member).permit(:login_name, :tos_agreement, :email, :newsletter, :send_harvest_reminder)
|
||||
end
|
||||
|
||||
def member_json_fields
|
||||
|
||||
@@ -21,6 +21,10 @@ class PostsController < ApplicationController
|
||||
def new
|
||||
@post = Post.new
|
||||
@forum = Forum.find_by(id: params[:forum_id])
|
||||
if params[:crop_id]
|
||||
@crop = Crop.friendly.find(params[:crop_id])
|
||||
@post.body = "[#{@crop.name}](crop)"
|
||||
end
|
||||
respond_with(@post)
|
||||
end
|
||||
|
||||
|
||||
20
app/controllers/push_subscriptions_controller.rb
Normal file
20
app/controllers/push_subscriptions_controller.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PushSubscriptionsController < ApplicationController
|
||||
before_action :authenticate_member!
|
||||
|
||||
def create
|
||||
subscription = current_member.push_subscriptions.find_or_initialize_by(endpoint: params[:subscription][:endpoint])
|
||||
subscription.update(
|
||||
p256dh: params[:subscription][:keys][:p256dh],
|
||||
auth: params[:subscription][:keys][:auth]
|
||||
)
|
||||
head :ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
subscription = current_member.push_subscriptions.find_by(endpoint: params[:endpoint])
|
||||
subscription&.destroy
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
@@ -30,7 +30,7 @@ class SeedsController < DataController
|
||||
page: params[:page],
|
||||
limit: 30,
|
||||
boost_by: [:created_at],
|
||||
load: false
|
||||
load: (request.format.csv? ? { include: %i(crop owner) } : false)
|
||||
)
|
||||
|
||||
respond_with(@seeds)
|
||||
|
||||
35
app/jobs/push_notification_job.rb
Normal file
35
app/jobs/push_notification_job.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PushNotificationJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(*args)
|
||||
Member.where.not(timezone: nil).pluck(:timezone).uniq.each do |timezone|
|
||||
Time.use_zone(timezone) do
|
||||
if Time.zone.now.hour == 8
|
||||
Member.where(timezone: timezone).each do |member|
|
||||
send_planting_notifications(member)
|
||||
send_activity_notifications(member)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_planting_notifications(member)
|
||||
member.plantings.active.annual.each do |planting|
|
||||
if planting.finish_is_predicatable? && (planting.late? || planting.super_late?)
|
||||
PushNotificationService.new(member, "Your #{planting.crop_name} planting is ready to be marked as finished.").send
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def send_activity_notifications(member)
|
||||
due_activities = member.activities.where(due_date: Date.today, finished: false)
|
||||
due_activities.each do |activity|
|
||||
PushNotificationService.new(member, "Activity due: #{activity.name}").send
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -57,6 +57,19 @@ class NotifierMailer < ApplicationMailer
|
||||
mail(to: @member.email, subject: @subject) if @member.send_planting_reminder
|
||||
end
|
||||
|
||||
def harvest_reminder(member)
|
||||
@member = member
|
||||
@plantings = @member.plantings.active.select(&:harvest_in_next_week?)
|
||||
@sitename = ENV.fetch('GROWSTUFF_SITE_NAME', nil)
|
||||
@subject = I18n.t('notifier_mailer.harvest_reminder.subject', sitename: @sitename)
|
||||
|
||||
# Encrypting
|
||||
message = { member_id: @member.id, type: :send_harvest_reminder }
|
||||
@signed_message = verifier.generate(message)
|
||||
|
||||
mail(to: @member.email, subject: @subject) if @member.send_harvest_reminder
|
||||
end
|
||||
|
||||
def new_crop_request(member, request)
|
||||
@member = member
|
||||
@request = request
|
||||
|
||||
@@ -4,7 +4,6 @@ class Activity < ApplicationRecord
|
||||
extend FriendlyId
|
||||
include Ownable
|
||||
include Finishable
|
||||
include SearchActivities
|
||||
include Likeable
|
||||
|
||||
belongs_to :garden, optional: true
|
||||
@@ -46,4 +45,17 @@ class Activity < ApplicationRecord
|
||||
def planting_slug
|
||||
planting&.crop&.slug
|
||||
end
|
||||
|
||||
scope :active, -> { where(finished: [false, nil]) }
|
||||
|
||||
def self.homepage_records(limit)
|
||||
# Get the latest activity for each owner, then return the latest 'limit' of those
|
||||
Activity.where(id: Activity.unscoped.select("DISTINCT ON (owner_id) id").order("owner_id, created_at DESC"))
|
||||
.order(created_at: :desc)
|
||||
.limit(limit)
|
||||
end
|
||||
|
||||
def self.reindex(refresh: false); end
|
||||
|
||||
def reindex(refresh: false); end
|
||||
end
|
||||
|
||||
@@ -60,10 +60,16 @@ module PredictHarvest
|
||||
def before_harvest_time?
|
||||
first_harvest_predicted_at.present? &&
|
||||
harvests.empty? &&
|
||||
first_harvest_predicted_at.present? &&
|
||||
first_harvest_predicted_at > Time.zone.today
|
||||
end
|
||||
|
||||
def harvest_in_next_week?
|
||||
first_harvest_predicted_at.present? &&
|
||||
harvests.empty? &&
|
||||
first_harvest_predicted_at >= Time.zone.today &&
|
||||
first_harvest_predicted_at <= Time.zone.today + 7.days
|
||||
end
|
||||
|
||||
def harvest_months
|
||||
Rails.cache.fetch("#{cache_key_with_version}/harvest_months", expires_in: 5.minutes) do
|
||||
neighbours_for_harvest_predictions.where.not(harvested_at: nil)
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SearchActivities
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
searchkick merge_mappings: true,
|
||||
settings: { number_of_shards: 1, number_of_replicas: 0 },
|
||||
mappings: {
|
||||
properties: {
|
||||
active: { type: :boolean },
|
||||
created_at: { type: :integer },
|
||||
updated_at: { type: :integer },
|
||||
due_date: { type: :date }
|
||||
}
|
||||
}
|
||||
|
||||
def search_data
|
||||
{
|
||||
slug:,
|
||||
active:,
|
||||
finished: finished?,
|
||||
name:,
|
||||
due_date:,
|
||||
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
|
||||
owner_id:,
|
||||
owner_login_name:,
|
||||
owner_slug:,
|
||||
|
||||
# timestamps
|
||||
created_at: created_at.to_i,
|
||||
updated_at: updated_at.to_i
|
||||
}
|
||||
end
|
||||
|
||||
def self.homepage_records(limit)
|
||||
records = []
|
||||
owners = []
|
||||
1..limit.times do
|
||||
where = {
|
||||
# photos_count: { gt: 0 },
|
||||
owner_id: { not: owners }
|
||||
}
|
||||
one_record = search('*',
|
||||
limit: 1,
|
||||
where:,
|
||||
boost_by: [:created_at],
|
||||
load: false).first
|
||||
return records if one_record.nil?
|
||||
|
||||
owners << one_record.owner_id
|
||||
records << one_record
|
||||
end
|
||||
records
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -79,6 +79,7 @@ class Member < ApplicationRecord
|
||||
scope :interesting, -> { confirmed.located.recently_signed_in.has_plantings }
|
||||
scope :has_plantings, -> { joins(:plantings).group("members.id") }
|
||||
scope :wants_reminders, -> { where(send_planting_reminder: true) }
|
||||
scope :wants_harvest_reminders, -> { where(send_harvest_reminder: true) }
|
||||
|
||||
# Include default devise modules. Others available are:
|
||||
# :token_authenticatable, :confirmable,
|
||||
@@ -161,7 +162,7 @@ class Member < ApplicationRecord
|
||||
end
|
||||
|
||||
def unread_count
|
||||
receipts.where(is_read: false).count
|
||||
@unread_count ||= receipts.where(is_read: false).count
|
||||
end
|
||||
|
||||
def self.login_name_or_email(login)
|
||||
|
||||
5
app/models/push_subscription.rb
Normal file
5
app/models/push_subscription.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PushSubscription < ApplicationRecord
|
||||
belongs_to :member
|
||||
end
|
||||
31
app/services/push_notification_service.rb
Normal file
31
app/services/push_notification_service.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PushNotificationService
|
||||
def initialize(member, message)
|
||||
@member = member
|
||||
@message = message
|
||||
end
|
||||
|
||||
def send
|
||||
@member.push_subscriptions.each do |subscription|
|
||||
begin
|
||||
WebPush.payload_send(
|
||||
message: JSON.generate(title: 'Growstuff', body: @message),
|
||||
endpoint: subscription.endpoint,
|
||||
p256dh: subscription.p256dh,
|
||||
auth: subscription.auth,
|
||||
vapid: {
|
||||
subject: "mailto:#{ENV.fetch('GROWSTUFF_EMAIL', 'noreply@growstuff.org')}",
|
||||
public_key: ENV['GROWSTUFF_VAPID_PUBLIC_KEY'],
|
||||
private_key: ENV['GROWSTUFF_VAPID_PRIVATE_KEY']
|
||||
}
|
||||
)
|
||||
rescue WebPush::InvalidSubscription => e
|
||||
# A subscription can become invalid if the user revokes the permission.
|
||||
# In this case, we should delete the subscription.
|
||||
subscription.destroy
|
||||
Rails.logger.info "Subscription deleted because it was invalid: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,5 +5,8 @@
|
||||
= render 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
|
||||
= render 'harvests/modal', harvest: Harvest.new(crop: @crop, owner: current_member)
|
||||
= render 'seeds/modal', seed: Seed.new(crop: @crop, owner: current_member)
|
||||
= link_to new_post_path(crop_id: crop.slug), class: 'btn', id: 'post-button' do
|
||||
= post_icon
|
||||
Post
|
||||
- if active_plantings.any?
|
||||
= render 'plantings/failed_modal', crop: crop, active_plantings: active_plantings
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Nobody has posted about #{crop.name.pluralize} yet.
|
||||
%p
|
||||
- if can? :create, Post
|
||||
= link_to "Post something", new_post_path, class: 'btn btn-default'
|
||||
= link_to "Post something", new_post_path(crop_id: crop.slug), class: 'btn btn-default'
|
||||
- else
|
||||
= render partial: "shared/signin_signup",
|
||||
locals: { to: "post your tips and experiences growing #{crop.name.pluralize}" }
|
||||
|
||||
@@ -44,8 +44,10 @@ csv.headers *all_headers
|
||||
@crops.each do |c|
|
||||
csv.row c do |csv, crop|
|
||||
|
||||
csv.cells :id, :name, :en_wikipedia_url
|
||||
csv.cell :growstuff_url, crop_url(c)
|
||||
csv.cell :id, c.id
|
||||
csv.cell :name, c.name
|
||||
csv.cell :en_wikipedia_url, c.en_wikipedia_url
|
||||
csv.cell :growstuff_url, crop_url(slug: c.slug)
|
||||
|
||||
if c.scientific_names.any?
|
||||
csv.cell :default_scientific_name, c.default_scientific_name
|
||||
@@ -58,10 +60,10 @@ csv.headers *all_headers
|
||||
end
|
||||
|
||||
csv.cell :plantings_count, c.plantings_count || 0
|
||||
csv.cell :seeds_count, c.seeds.size[]
|
||||
csv.cell :seeds_count, c.seeds.size
|
||||
csv.cell :harvests_count, c.harvests.size
|
||||
|
||||
# Sunniness
|
||||
# Sunniness
|
||||
|
||||
sunniness = c.sunniness
|
||||
sunniness_rec = sunniness.max_by{|k,v| v}
|
||||
@@ -74,7 +76,7 @@ csv.headers *all_headers
|
||||
|
||||
# Planted from
|
||||
|
||||
planted_from = c.planted_from
|
||||
planted_from = c.planted_from
|
||||
planted_from_rec = planted_from.max_by{|k,v| v}
|
||||
if planted_from_rec
|
||||
csv.cell :plant_from_recommendation, planted_from_rec[0]
|
||||
@@ -105,12 +107,11 @@ csv.headers *all_headers
|
||||
csv.cell col, harvested_plant_parts[pp] || 0
|
||||
end
|
||||
|
||||
csv.cell :added_by_member_id, c.creator.id
|
||||
csv.cell :added_by_member_name, c.creator.to_s
|
||||
csv.cell :added_by_member_id, c.creator&.id
|
||||
csv.cell :added_by_member_name, c.creator&.to_s
|
||||
csv.cell :date_added, c.created_at.to_fs(:db)
|
||||
csv.cell :last_modified, c.updated_at.to_fs(:db)
|
||||
|
||||
csv.cell :license, "CC-BY-SA Growstuff http://growstuff.org/"
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
= f.check_box :send_planting_reminder
|
||||
Receive regular reminders to track your planting and harvesting.
|
||||
|
||||
.form-group
|
||||
.col-md-offset-2.col-md-8.checkbox
|
||||
%label
|
||||
= f.check_box :send_harvest_reminder
|
||||
Receive regular reminders of upcoming harvests.
|
||||
|
||||
.form-group
|
||||
.col-md-offset-2.col-md-8.checkbox
|
||||
%label
|
||||
|
||||
@@ -32,9 +32,11 @@
|
||||
- else
|
||||
%meta{name: "description", content: "Growstuff is a community of food gardeners. Let's learn to grow food together. All our data is open data."}
|
||||
= csrf_meta_tags
|
||||
%meta{name: "vapid-public-key", content: ENV['GROWSTUFF_VAPID_PUBLIC_KEY']}
|
||||
= stylesheet_link_tag "application", media: "all"
|
||||
|
||||
%link{ href: path_to_image("growstuff-apple-touch-icon-precomposed.png"), rel: "apple-touch-icon-precomposed" }
|
||||
%link{ href: "https://fonts.googleapis.com/css?family=Modak|Raleway&display=swap", rel: "stylesheet" }
|
||||
= favicon_link_tag 'favicon.ico'
|
||||
= serviceworker_js_tag
|
||||
= tag("meta", name: "google-site-verification", content: "j249rPGdBqZ7gcShcdsSXCnGN5lqCuTISJnlQXxOfu4")
|
||||
|
||||
8
app/views/members/_notifications.html.haml
Normal file
8
app/views/members/_notifications.html.haml
Normal file
@@ -0,0 +1,8 @@
|
||||
.card.mt-3
|
||||
.card-body
|
||||
%h5.card-title Notifications
|
||||
%p
|
||||
Install Growstuff as a Progressive Web App (PWA) to get notifications on your device.
|
||||
Look for the "Add to Home Screen" option in your browser's menu.
|
||||
%button.btn.btn-primary#enable-push-notifications
|
||||
Enable Push Notifications
|
||||
@@ -73,6 +73,8 @@
|
||||
|
||||
= render 'members/follow_buttons', member: @member
|
||||
|
||||
= render "notifications", member: @member if can?(:update, @member)
|
||||
|
||||
- if can?(:destroy, @member)
|
||||
%hr/
|
||||
= link_to admin_member_path(slug: @member.slug), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-block btn-light text-danger' do
|
||||
|
||||
33
app/views/notifier_mailer/harvest_reminder.html.haml
Normal file
33
app/views/notifier_mailer/harvest_reminder.html.haml
Normal file
@@ -0,0 +1,33 @@
|
||||
%p Hello #{@member.login_name},
|
||||
|
||||
%h2= t('notifier_mailer.harvest_reminder.heading')
|
||||
|
||||
%p= t('notifier_mailer.harvest_reminder.intro')
|
||||
|
||||
%ul
|
||||
- @plantings.each do |planting|
|
||||
%li
|
||||
= render 'planting', planting: planting
|
||||
(Predicted harvest date: #{planting.first_harvest_predicted_at.to_date})
|
||||
|
||||
%p
|
||||
Harvested anything lately?
|
||||
= link_to "Track your harvests here.", new_harvest_url, class: 'btn'
|
||||
|
||||
%p
|
||||
Track and predict your entire garden, and keep your garden records up to date at
|
||||
= link_to member_gardens_url(@member), class: 'btn' do
|
||||
your garden overview
|
||||
and on
|
||||
= link_to member_url(@member) do
|
||||
your profile page
|
||||
|
||||
%h4
|
||||
See you soon on #{@sitename}!
|
||||
|
||||
= render partial: 'signature'
|
||||
|
||||
%hr/
|
||||
%p
|
||||
Don't want to get these emails any more?
|
||||
= link_to t('notifier_mailer.harvest_reminder.unsubscribe'), unsubscribe_member_url(message: @signed_message)
|
||||
@@ -32,7 +32,7 @@ csv.headers :id,
|
||||
|
||||
csv.cell :quantity
|
||||
|
||||
csv.cell :plant_before, s.plant_before ? s.plant_before.to_s(:db) : ''
|
||||
csv.cell :plant_before, s.plant_before ? s.plant_before.to_fs(:db) : ''
|
||||
|
||||
csv.cell :tradable_to
|
||||
csv.cell :from_location, s.owner.location
|
||||
|
||||
@@ -16,10 +16,6 @@ Rails.application.configure do
|
||||
config.consider_all_requests_local = false
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Attempt to read encrypted secrets from `config/secrets.yml.enc`.
|
||||
# Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
|
||||
# `config/secrets.yml.key`.
|
||||
config.read_encrypted_secrets = true
|
||||
|
||||
# Disable serving static files from the `/public` folder by default since
|
||||
# Apache or NGINX already handles this.
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
# config/initializers/sidekiq.rb
|
||||
|
||||
Sidekiq.configure_server do |config|
|
||||
config.redis = { url: 'redis://localhost:6379/0', namespace: "app3_sidekiq_#{Rails.env}" }
|
||||
config.redis = { url: 'redis://localhost:6379/0' }
|
||||
end
|
||||
|
||||
Sidekiq.configure_client do |config|
|
||||
config.redis = { url: 'redis://localhost:6379/0', namespace: "app3_sidekiq_#{Rails.env}" }
|
||||
config.redis = { url: 'redis://localhost:6379/0' }
|
||||
end
|
||||
|
||||
@@ -456,3 +456,9 @@ en:
|
||||
all: Not authorized to %{action} %{subject}.
|
||||
read:
|
||||
notification: You must be signed in to view notifications.
|
||||
notifier_mailer:
|
||||
harvest_reminder:
|
||||
subject: "Upcoming harvests in your garden - %{sitename}"
|
||||
heading: "Upcoming harvests in your garden"
|
||||
intro: "Based on our predictions, the following plantings in your garden are likely to be ready for harvest within the next week:"
|
||||
unsubscribe: "Unsubscribe from harvest reminders"
|
||||
|
||||
@@ -21,6 +21,7 @@ Rails.application.routes.draw do
|
||||
match '/members/:id/finish_signup' => 'members#finish_signup', via: %i(get patch), as: :finish_signup
|
||||
|
||||
resources :authentications, only: %i(create destroy)
|
||||
resources :push_subscriptions, only: %i(create destroy)
|
||||
|
||||
get "home/index"
|
||||
get '/community-gardens', to: 'home#community_gardens'
|
||||
|
||||
@@ -15,7 +15,5 @@ class AddActivities < ActiveRecord::Migration[7.1]
|
||||
end
|
||||
|
||||
add_column :members, :activities_count, :integer
|
||||
|
||||
Activity.reindex
|
||||
end
|
||||
end
|
||||
|
||||
7
db/migrate/20240929041436_add_timezone_to_members.rb
Normal file
7
db/migrate/20240929041436_add_timezone_to_members.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddTimezoneToMembers < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :members, :timezone, :string
|
||||
end
|
||||
end
|
||||
15
db/migrate/20240929041437_create_push_subscriptions.rb
Normal file
15
db/migrate/20240929041437_create_push_subscriptions.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreatePushSubscriptions < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :push_subscriptions do |t|
|
||||
t.references :member, null: false, foreign_key: true
|
||||
t.string :endpoint, null: false
|
||||
t.string :p256dh, null: false
|
||||
t.string :auth, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
add_index :push_subscriptions, :endpoint, unique: true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddSendHarvestReminderToMembers < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :members, :send_harvest_reminder, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
||||
47
db/schema.rb
47
db/schema.rb
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_12_01_045000) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2026_04_29_132911) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -786,6 +786,8 @@ ActiveRecord::Schema[7.2].define(version: 2025_12_01_045000) do
|
||||
t.string "facebook_handle"
|
||||
t.string "bluesky_handle"
|
||||
t.string "other_url"
|
||||
t.string "timezone"
|
||||
t.boolean "send_harvest_reminder", default: true, null: false
|
||||
t.index ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true
|
||||
t.index ["discarded_at"], name: "index_members_on_discarded_at"
|
||||
t.index ["email"], name: "index_members_on_email", unique: true
|
||||
@@ -896,6 +898,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_12_01_045000) do
|
||||
t.integer "harvests_count", default: 0
|
||||
t.integer "likes_count", default: 0
|
||||
t.boolean "failed", default: false, null: false
|
||||
t.boolean "from_other_source"
|
||||
t.integer "overall_rating"
|
||||
t.index ["crop_id"], name: "index_plantings_on_crop_id"
|
||||
t.index ["garden_id"], name: "index_plantings_on_garden_id"
|
||||
@@ -919,6 +922,43 @@ ActiveRecord::Schema[7.2].define(version: 2025_12_01_045000) do
|
||||
t.index ["slug"], name: "index_posts_on_slug", unique: true
|
||||
end
|
||||
|
||||
create_table "problem_posts", force: :cascade do |t|
|
||||
t.bigint "problem_id"
|
||||
t.bigint "post_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["post_id"], name: "index_problem_posts_on_post_id"
|
||||
t.index ["problem_id", "post_id"], name: "index_problem_posts_on_problem_id_and_post_id", unique: true
|
||||
t.index ["problem_id"], name: "index_problem_posts_on_problem_id"
|
||||
end
|
||||
|
||||
create_table "problems", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "reason_for_rejection"
|
||||
t.string "rejection_notes"
|
||||
t.string "approval_status", default: "pending", null: false
|
||||
t.bigint "requester_id"
|
||||
t.bigint "creator_id"
|
||||
t.string "slug"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["creator_id"], name: "index_problems_on_creator_id"
|
||||
t.index ["name"], name: "index_problems_on_name"
|
||||
t.index ["requester_id"], name: "index_problems_on_requester_id"
|
||||
t.index ["slug"], name: "index_problems_on_slug"
|
||||
end
|
||||
|
||||
create_table "push_subscriptions", force: :cascade do |t|
|
||||
t.bigint "member_id", null: false
|
||||
t.string "endpoint", null: false
|
||||
t.string "p256dh", null: false
|
||||
t.string "auth", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["endpoint"], name: "index_push_subscriptions_on_endpoint", unique: true
|
||||
t.index ["member_id"], name: "index_push_subscriptions_on_member_id"
|
||||
end
|
||||
|
||||
create_table "roles", id: :serial, force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.text "description"
|
||||
@@ -991,5 +1031,10 @@ ActiveRecord::Schema[7.2].define(version: 2025_12_01_045000) do
|
||||
add_foreign_key "photo_associations", "crops"
|
||||
add_foreign_key "photo_associations", "photos"
|
||||
add_foreign_key "plantings", "seeds", column: "parent_seed_id", name: "parent_seed", on_delete: :nullify
|
||||
add_foreign_key "problem_posts", "posts"
|
||||
add_foreign_key "problem_posts", "problems"
|
||||
add_foreign_key "problems", "members", column: "creator_id"
|
||||
add_foreign_key "problems", "members", column: "requester_id"
|
||||
add_foreign_key "push_subscriptions", "members"
|
||||
add_foreign_key "seeds", "plantings", column: "parent_planting_id", name: "parent_planting", on_delete: :nullify
|
||||
end
|
||||
|
||||
@@ -65,3 +65,9 @@ MAILGUN_SMTP_SERVER=""
|
||||
# In production, replace them with real ones
|
||||
RECAPTCHA_SITE_KEY="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
|
||||
RECAPTCHA_SECRET_KEY="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"
|
||||
|
||||
# VAPID keys for web push notifications
|
||||
# These are insecure and should be replaced with real keys in production
|
||||
# Generate new keys with `bundle exec rake webpush:generate_keys`
|
||||
GROWSTUFF_VAPID_PUBLIC_KEY="BFf_pM3_3q0g1hIUiWf_nQdYj524I4E-mp3jW_j_7X-B-xWpW-j_8X_8X_8X_8X_8X_8X_8X_8X_8"
|
||||
GROWSTUFF_VAPID_PRIVATE_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
@@ -48,8 +48,23 @@ namespace :growstuff do
|
||||
# Heroku scheduler only lets us run things daily, so this checks
|
||||
# Send on Monday
|
||||
if Time.zone.today.wday == 1
|
||||
Member.confirmed.wants_reminders.each do |m|
|
||||
Notifier.planting_reminder(m).deliver_now! unless m.plantings.active.empty?
|
||||
Member.confirmed.wants_reminders.find_each do |m|
|
||||
NotifierMailer.planting_reminder(m).deliver_later unless m.plantings.active.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "Send harvest reminder email"
|
||||
# usage: rake growstuff:send_harvest_reminders
|
||||
|
||||
task send_harvest_reminders: :environment do
|
||||
# Heroku scheduler only lets us run things daily, so this checks
|
||||
# Send on Wednesday
|
||||
if Time.zone.today.wday == 3
|
||||
Member.confirmed.wants_harvest_reminders.find_each do |m|
|
||||
if m.plantings.active.any?(&:harvest_in_next_week?)
|
||||
NotifierMailer.harvest_reminder(m).deliver_later
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,5 @@ namespace :search do
|
||||
Planting.reindex
|
||||
Harvest.reindex
|
||||
Seed.reindex
|
||||
Activity.reindex
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,6 +73,21 @@ describe CropsController do
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET CSV" do
|
||||
let!(:tomato) { create(:tomato, en_wikipedia_url: "https://en.wikipedia.org/wiki/Tomato") }
|
||||
before do
|
||||
Crop.reindex
|
||||
get :index, format: "csv"
|
||||
end
|
||||
|
||||
it { is_expected.to be_successful }
|
||||
it { expect(response.content_type).to eq("text/csv; charset=utf-8") }
|
||||
it "contains tomato", pending: "not properly functional" do
|
||||
expect(assigns(:crops)).not_to be_empty
|
||||
expect(response.body).to include("tomato")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'CREATE' do
|
||||
subject { put :create, params: crop_params }
|
||||
|
||||
|
||||
@@ -10,6 +10,20 @@ describe PostsController do
|
||||
{ author_id: member.id, subject: "blah", body: "blah blah" }
|
||||
end
|
||||
|
||||
describe '#new' do
|
||||
let(:crop) { create(:crop, name: 'Bush Bean') }
|
||||
|
||||
it 'pre-populates the body when crop_id is present' do
|
||||
get :new, params: { crop_id: crop.slug }
|
||||
expect(assigns(:post).body).to eq("[#{crop.name}](crop)")
|
||||
end
|
||||
|
||||
it 'does not pre-populate the body when crop_id is absent' do
|
||||
get :new
|
||||
expect(assigns(:post).body).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#index' do
|
||||
before do
|
||||
create_list(:post, 100)
|
||||
|
||||
@@ -21,5 +21,9 @@ FactoryBot.define do
|
||||
description { "Stake tomato" }
|
||||
planting
|
||||
end
|
||||
|
||||
trait :reindex do
|
||||
# Activity is not using elasticsearch anymore, so we don't need to reindex
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,5 +21,9 @@ FactoryBot.define do
|
||||
factory :forum_post do
|
||||
forum
|
||||
end
|
||||
|
||||
trait :reindex do
|
||||
# Post is not using elasticsearch, but this trait is used in some tests
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
28
spec/mailers/harvest_reminder_mailer_spec.rb
Normal file
28
spec/mailers/harvest_reminder_mailer_spec.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe NotifierMailer, type: :mailer do
|
||||
let(:member) { create(:member) }
|
||||
let(:mail) { NotifierMailer.harvest_reminder(member) }
|
||||
|
||||
it "has a greeting" do
|
||||
expect(mail.body.encoded).to match "Hello"
|
||||
end
|
||||
|
||||
context "when member has upcoming harvests" do
|
||||
let(:crop) { create(:crop, median_days_to_first_harvest: 20) }
|
||||
let!(:planting) { create(:planting, owner: member, crop: crop, planted_at: 15.days.ago) }
|
||||
let(:plantings) { [planting] }
|
||||
|
||||
it "lists the upcoming harvest" do
|
||||
expect(mail.body.encoded).to match "Upcoming harvests in your garden"
|
||||
expect(mail.body.encoded).to match planting.crop.name
|
||||
expect(mail.body.encoded).to match (Time.zone.today + 5.days).to_date.to_s
|
||||
end
|
||||
|
||||
it "has an unsubscribe link" do
|
||||
expect(mail.body.encoded).to match "Unsubscribe from harvest reminders"
|
||||
end
|
||||
end
|
||||
end
|
||||
42
spec/models/activity_spec.rb
Normal file
42
spec/models/activity_spec.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Activity do
|
||||
describe '.homepage_records' do
|
||||
let(:member1) { create(:member) }
|
||||
let(:member2) { create(:member) }
|
||||
|
||||
it 'returns the latest activities per owner' do
|
||||
create(:activity, owner: member1, created_at: 2.days.ago)
|
||||
latest_activity1 = create(:activity, owner: member1, created_at: 1.day.ago)
|
||||
latest_activity2 = create(:activity, owner: member2, created_at: Time.current)
|
||||
|
||||
records = described_class.homepage_records(10)
|
||||
expect(records).to contain_exactly(latest_activity1, latest_activity2)
|
||||
end
|
||||
|
||||
it 'respects the limit' do
|
||||
create(:activity, owner: member1)
|
||||
create(:activity, owner: member2)
|
||||
|
||||
records = described_class.homepage_records(1)
|
||||
expect(records.length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'active scope' do
|
||||
it 'returns activities that are not finished' do
|
||||
active_activity = create(:activity, finished: false)
|
||||
finished_activity = create(:activity, finished: true)
|
||||
|
||||
expect(described_class.active).to include(active_activity)
|
||||
expect(described_class.active).not_to include(finished_activity)
|
||||
end
|
||||
|
||||
it 'treats nil finished as active' do
|
||||
activity = create(:activity, finished: nil)
|
||||
expect(described_class.active).to include(activity)
|
||||
end
|
||||
end
|
||||
end
|
||||
41
spec/models/harvest_prediction_spec.rb
Normal file
41
spec/models/harvest_prediction_spec.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe "Harvest prediction logic" do
|
||||
let(:crop) { create(:crop, median_days_to_first_harvest: 20) }
|
||||
let(:planting) { create(:planting, crop: crop) }
|
||||
|
||||
describe "#harvest_in_next_week?" do
|
||||
it "is true if predicted harvest is in 5 days" do
|
||||
planting.planted_at = 15.days.ago
|
||||
expect(planting.harvest_in_next_week?).to be true
|
||||
end
|
||||
|
||||
it "is true if predicted harvest is today" do
|
||||
planting.planted_at = 20.days.ago
|
||||
expect(planting.harvest_in_next_week?).to be true
|
||||
end
|
||||
|
||||
it "is true if predicted harvest is in 7 days" do
|
||||
planting.planted_at = 13.days.ago
|
||||
expect(planting.harvest_in_next_week?).to be true
|
||||
end
|
||||
|
||||
it "is false if predicted harvest is in 8 days" do
|
||||
planting.planted_at = 12.days.ago
|
||||
expect(planting.harvest_in_next_week?).to be false
|
||||
end
|
||||
|
||||
it "is false if predicted harvest was yesterday" do
|
||||
planting.planted_at = 21.days.ago
|
||||
expect(planting.harvest_in_next_week?).to be false
|
||||
end
|
||||
|
||||
it "is false if there are already harvests" do
|
||||
planting.planted_at = 15.days.ago
|
||||
create(:harvest, planting: planting, owner: planting.owner, crop: planting.crop, harvested_at: 1.day.ago)
|
||||
expect(planting.harvest_in_next_week?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -50,7 +50,6 @@ RSpec.configure do |config|
|
||||
Photo.reindex
|
||||
Planting.reindex
|
||||
Seed.reindex
|
||||
Activity.reindex
|
||||
end
|
||||
|
||||
config.before(:suite) do
|
||||
|
||||
@@ -90,13 +90,19 @@ describe 'layouts/_header.html.haml', type: "view" do
|
||||
expect(rendered).to have_content 'Inbox'
|
||||
expect(rendered).not_to match(/Inbox \d+/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'has notifications' do
|
||||
it 'shows inbox count' do
|
||||
create(:notification, recipient: @member)
|
||||
render
|
||||
expect(rendered).to have_content 'Inbox 1'
|
||||
end
|
||||
context 'logged in, has notifications' do
|
||||
before do
|
||||
@member = create(:member)
|
||||
create(:notification, recipient: @member)
|
||||
sign_in @member
|
||||
controller.stub(:current_user) { @member }
|
||||
end
|
||||
|
||||
it 'shows inbox count' do
|
||||
render
|
||||
rendered.should have_content 'Inbox 1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user