Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
cdd02c3
Allow person to create a request directly in response to an offer
rsmithlal Aug 19, 2025
48c7392
Notifiy offer creators of a match when someone makes a request linked…
rsmithlal Aug 19, 2025
25e0e0c
Restrict the offer and request list results to exclude requests and o…
rsmithlal Aug 19, 2025
13509d6
If person has responded to an offer, prevent them from responding aga…
rsmithlal Aug 19, 2025
9e5ad4b
If request was created in response to an offer, show the offer that i…
rsmithlal Aug 19, 2025
4b2699f
Clearly identify offers and requests created by current-person or in …
rsmithlal Aug 19, 2025
aeaadeb
Filter requests and offers to exclude those that were creared in resp…
rsmithlal Aug 19, 2025
07a52d3
Rubocop fixes
rsmithlal Aug 19, 2025
97011e0
Allow person to directly create an offer or request in response to a …
rsmithlal Aug 20, 2025
a142ba5
She the offer/request creator on the show view
rsmithlal Aug 20, 2025
5b71f5a
Show source offer or request in the top of the form when responding t…
rsmithlal Aug 20, 2025
8146cf1
Show and allow setting offer and request status and urgency
rsmithlal Aug 20, 2025
f3b40a9
Restricts responses to open/matched sources; marks matched status
rsmithlal Aug 20, 2025
15f89cc
Update specs
rsmithlal Aug 20, 2025
caafc61
Rubocop Fixes
rsmithlal Aug 20, 2025
9c87d8a
Add posts url to navigation item route names
rsmithlal Aug 20, 2025
947bba4
Adjust offers and requests list sidebar to move add button to the sea…
rsmithlal Aug 20, 2025
b68463e
Rubocop fixes
rsmithlal Aug 20, 2025
c29afc0
Add diagram and docs for exchange system
rsmithlal Aug 21, 2025
cd23661
Update agent instructions to generate and maintain docs and diagrams
rsmithlal Aug 21, 2025
6574547
Add pull request template and contributing guidelines
rsmithlal Aug 21, 2025
b146f74
Enhance agreement and response link handling
rsmithlal Aug 21, 2025
4c0db86
Refactor notification handling in controllers and add NotificationRea…
rsmithlal Aug 21, 2025
bafc0ed
Add docs and flow diagram for notifications system
rsmithlal Aug 21, 2025
df0fafd
Update diagrams
rsmithlal Aug 21, 2025
49a2b60
Add docs to readmes
rsmithlal Aug 21, 2025
f1f0dbf
Add docs and diagrams for user accounts and invitation flows
rsmithlal Aug 21, 2025
10d0c89
Enhance diagram rendering script with force option and improved outpu…
rsmithlal Aug 21, 2025
c575e44
Add invitation requirement details to registration forms and document…
rsmithlal Aug 21, 2025
3e39f51
Update docs developer guides to explain rendering diagrams
rsmithlal Aug 21, 2025
6de2b6e
Update robot instructions to raise awareness of platform registrartio…
rsmithlal Aug 21, 2025
3923b0d
Update robot instructions to use i18n-tasks to ensure translation health
rsmithlal Aug 21, 2025
498e6ae
Add coverage report upload step to CI workflow
rsmithlal Aug 21, 2025
3810019
Refactor flash messages to use I18n translations across controllers a…
rsmithlal Aug 21, 2025
15e006b
Add docs and flow diagram for Role Based Access Control (RBAC)
rsmithlal Aug 21, 2025
8a709cf
Add docs and diagrams of the content management and navigation systems
rsmithlal Aug 21, 2025
c41e212
Add privacy practices and metrics documentation to enhance data handl…
rsmithlal Aug 21, 2025
9097f23
Add democratic-by-design docs
rsmithlal Aug 21, 2025
db789ef
Adjust i18n-health job to use ruby 3.4.4
rsmithlal Aug 21, 2025
a701242
feat: Implement event RSVP functionality with ICS export
rsmithlal Aug 21, 2025
8f96fbd
Update coverage report generation for GH action
rsmithlal Aug 21, 2025
33a39ad
feat: Implement source type validation in Joatu controllers and enhan…
rsmithlal Aug 21, 2025
f45e2c3
chore: Remove unnecessary whitespace in JoatuController
rsmithlal Aug 21, 2025
d921f33
feat: Enhance security guidelines in documentation for code generatio…
rsmithlal Aug 21, 2025
de3c632
Add event reminder functionality with associated tests and mailer
rsmithlal Aug 21, 2025
a233dac
feat: Enhance event reminder and update mailers with new templates an…
rsmithlal Aug 21, 2025
161c728
feat: Refactor event reminder job and notifier to use params for even…
rsmithlal Aug 21, 2025
fb62b95
chore: Remove test_person.rb file as it is no longer needed
rsmithlal Aug 21, 2025
333ed2f
refactor: Simplify error handling tests in EventReminderJob spec
rsmithlal Aug 21, 2025
e7939cf
Add notifications queue to dummy app
rsmithlal Aug 21, 2025
71b9e47
feat: Enhance event and notification systems with comprehensive remin…
rsmithlal Aug 21, 2025
b0b810b
Normalize existing translations
rsmithlal Aug 21, 2025
9e43d2e
Add missing translations
rsmithlal Aug 21, 2025
9359464
fix: Update notification titles to include conversation context in En…
rsmithlal Aug 21, 2025
f236bab
fix: Simplify new message notification title in French locale
rsmithlal Aug 21, 2025
fdd1618
fix: Update time-related translations for improved clarity in English…
rsmithlal Aug 21, 2025
e0052ed
Merge branch 'main' into feature/exchange
rsmithlal Aug 21, 2025
14860ad
refactor: Update event flow and documentation to enhance event hostin…
rsmithlal Aug 21, 2025
e2ea8af
refactor: Organize event host spec to improve clarity and structure
rsmithlal Aug 21, 2025
7b6c579
feat: Add better_together_event_hosts table to schema
rsmithlal Aug 21, 2025
b15d50c
test: Add validation trigger for event host defaulting to creator
rsmithlal Aug 21, 2025
7cd9c12
update i18n check
rsmithlal Aug 21, 2025
86d6134
ci(i18n): install dev/test gems and use bundler-cache; add bin/i18n h…
rsmithlal Aug 21, 2025
f6326bf
ci(i18n): ensure bundler uses dev/test groups during cache; add i18n-…
rsmithlal Aug 21, 2025
da26be1
ci(i18n): make bin/i18n compatible across i18n-tasks versions (fallba…
rsmithlal Aug 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 125 additions & 1 deletion app/controllers/better_together/joatu/offers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module BetterTogether
module Joatu
# CRUD for BetterTogether::Joatu::Offer
class OffersController < JoatuController
class OffersController < JoatuController # rubocop:todo Metrics/ClassLength
def show
super
mark_match_notifications_read_for(resource_instance)
Expand Down Expand Up @@ -66,6 +66,130 @@
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity

def respond_with_request
source = set_resource_instance
authorize_resource
redirect_to new_joatu_request_path(source_type: resource_class.to_s, source_id: source.id)
end

# Render new with optional prefill from a source Request/Offer
def new # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength
resource_instance
# If source params were provided, load and authorize the source so the view can safely render it
if (source_type = params[:source_type].presence) && (source_id = params[:source_id].presence)
source_klass = source_type.to_s.safe_constantize
@source = source_klass&.find_by(id: source_id)
begin
authorize @source if @source
rescue Pundit::NotAuthorizedError
render_not_found and return
end

# Only allow responding to sources that are open or already matched
# :todo Metrics/BlockNesting, rubocop:todo Layout/LineLength, rubocop:todo Metrics/PerceivedComplexity,
redirect_to url_for(@source.becomes(@source.class)),
alert: 'Cannot create a response for a source that is not open or matched.' and return

end

apply_source_prefill_offer(resource_instance)
end

# rubocop:todo Metrics/PerceivedComplexity
# rubocop:todo Metrics/MethodLength
# rubocop:todo Metrics/AbcSize
def create # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
resource_instance(resource_params)
authorize_resource

respond_to do |format| # rubocop:todo Metrics/BlockLength
if @resource.save
# Controller-side fallback: if source params were provided but no nested response_link was created,
# create a ResponseLink linking the source Request -> this Offer.
source_type = params[:source_type] || params.dig(resource_name, :source_type)
source_id = params[:source_id] || params.dig(resource_name, :source_id)

if source_type == 'BetterTogether::Joatu::Request' && source_id.present?
source = BetterTogether::Joatu::Request.find_by(id: source_id)
if source
# rubocop:todo Metrics/BlockNesting
if source.respond_to?(:status) && !%w[open matched].include?(source.status)
Rails.logger.warn(
"Not creating response link: source #{source.id} status #{source.status} not respondable"
)
elsif !BetterTogether::Joatu::ResponseLink.exists?(source: source, response: @resource)
BetterTogether::Joatu::ResponseLink.create(source: source, response: @resource,
creator_id: helpers.current_person&.id)
end
# rubocop:enable Metrics/BlockNesting
end
end

format.html do
redirect_to url_for(@resource.becomes(resource_class)),
notice: "#{resource_class.model_name.human} was successfully created."
end
format.turbo_stream do
flash.now[:notice] = "#{resource_class.model_name.human} was successfully created."
redirect_to url_for(@resource.becomes(resource_class))
end
else
format.turbo_stream do
render status: :unprocessable_entity, turbo_stream: [
turbo_stream.replace(helpers.dom_id(@resource, 'form'),
partial: 'form',
locals: { resource_name.to_sym => @resource }),
turbo_stream.update('form_errors',
partial: 'layouts/better_together/errors',
locals: { object: @resource })
]
end
format.html { render :new, status: :unprocessable_entity }
end
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity

private

# Build an offer prefilled from a source Request when source params are present
# rubocop:todo Metrics/PerceivedComplexity
# rubocop:todo Metrics/MethodLength
# rubocop:todo Metrics/AbcSize
def apply_source_prefill_offer(offer) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
return unless offer

source_type = params[:source_type] || params.dig(resource_name, :source_type)
source_id = params[:source_id] || params.dig(resource_name, :source_id)

return unless source_type == 'BetterTogether::Joatu::Request' && source_id.present?

source = BetterTogether::Joatu::Request.find_by(id: source_id)
return unless source
# Do not build nested response_link if source is not respondable
return unless source.respond_to?(:status) ? %w[open matched].include?(source.status) : true

offer.name ||= source.name
offer.description ||= source.description
offer.target_type ||= source.target_type if source.respond_to?(:target_type)
offer.target_id ||= source.target_id if source.respond_to?(:target_id)
offer.urgency ||= source.urgency if source.respond_to?(:urgency)
offer.address || offer.build_address
if source.respond_to?(:categories) && offer.category_ids.blank?
offer.category_ids = source.categories.pluck(:id)
end

return unless offer.response_links_as_response.blank?

offer.response_links_as_response.build(source_type: source.class.to_s, source_id: source.id,
creator_id: helpers.current_person&.id)
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity

protected

def resource_class
Expand Down
69 changes: 68 additions & 1 deletion app/controllers/better_together/joatu/requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,74 @@
@matches = BetterTogether::Joatu::Matchmaker.match(@joatu_request)
end

protected
# Redirect to new offer form prefilled from a source Request
def respond_with_offer
source = set_resource_instance
authorize_resource
redirect_to new_joatu_offer_path(source_type: BetterTogether::Joatu::Request.to_s, source_id: source.id)
end

# Render new with optional prefill from a source Offer/Request
def new # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
resource_instance
# If source params were provided, load and authorize the source so the view can safely render it
if (source_type = params[:source_type].presence) && (source_id = params[:source_id].presence)
source_klass = source_type.to_s.safe_constantize
@source = source_klass&.find_by(id: source_id)
begin
authorize @source if @source
rescue Pundit::NotAuthorizedError
render_not_found and return
end

# Only allow responding to sources that are open or already matched
if @source.respond_to?(:status) && !%w[open matched].include?(@source.status)
redirect_to url_for(@source.becomes(@source.class)),
alert: 'Cannot create a response for a source that is not open or matched.' and return
end
end

apply_source_prefill(resource_instance)
end

private

# rubocop:todo Metrics/PerceivedComplexity
# rubocop:todo Metrics/MethodLength
# rubocop:todo Metrics/AbcSize
def apply_source_prefill(request) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
return unless request

# Accept source params either at top-level (hidden_field_tag in new) or nested inside the form params
source_type = params[:source_type] || params.dig(resource_name, :source_type)
source_id = params[:source_id] || params.dig(resource_name, :source_id)

return unless source_type == 'BetterTogether::Joatu::Offer' && source_id.present?

source = BetterTogether::Joatu::Offer.find_by(id: source_id)
return unless source
# Do not build nested response_link if source is not respondable
return unless source.respond_to?(:status) ? %w[open matched].include?(source.status) : true

request.name ||= source.name
request.description ||= source.description
request.target_type ||= source.target_type if source.respond_to?(:target_type)
request.target_id ||= source.target_id if source.respond_to?(:target_id)
request.urgency ||= source.urgency if source.respond_to?(:urgency)
request.address || request.build_address
if source.respond_to?(:categories) && request.category_ids.blank?
request.category_ids = source.categories.pluck(:id)
end

# Build a nested response_link so the form's fields_for will render hidden fields
return unless request.response_links_as_response.blank?

request.response_links_as_response.build(source_type: source.class.to_s, source_id: source.id,
creator_id: helpers.current_person&.id)
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity

def resource_class
::BetterTogether::Joatu::Request
Expand Down
90 changes: 90 additions & 0 deletions app/controllers/better_together/joatu/response_links_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

module BetterTogether
module Joatu
class ResponseLinksController < JoatuController # rubocop:todo Style/Documentation
before_action :authenticate_user!
# This controller doesn't call Pundit's `authorize` in the create flow
# because it builds responses from an existing source. The global
# `after_action :verify_authorized` from ResourceController would raise
# an AuthorizationNotPerformedError after a redirect which can lead to a
# DoubleRenderError (redirect then error render). Skip the check here.
skip_after_action :verify_authorized

def create # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
# Create a request from offer or offer from request, linking them via ResponseLink
source_type = params[:source_type]
source_id = params[:source_id]

unless source_type && source_id
return redirect_back fallback_location: joatu_hub_path,
alert: 'Invalid source'
end

source = source_type.constantize.find_by(id: source_id)
return redirect_back fallback_location: joatu_hub_path, alert: 'Source not found' unless source

# Only allow creating responses against sources that are open or already matched
if source.respond_to?(:status) && !%w[open matched].include?(source.status)
return redirect_back fallback_location: joatu_hub_path,
alert: 'Cannot respond to a source that is not open or matched.'
end

if source.is_a?(BetterTogether::Joatu::Offer)
# Build a new Request from offer details
request = BetterTogether::Joatu::Request.new
request.name = source.name
request.description = source.description
request.creator_id = helpers.current_person&.id
request.target_type = source.target_type if source.respond_to?(:target_type)
request.target_id = source.target_id if source.respond_to?(:target_id)
request.urgency = source.urgency if source.respond_to?(:urgency)
request.address_id = source.address_id if source.respond_to?(:address_id)
request.category_ids = source.categories.pluck(:id) if source.respond_to?(:categories)

if request.save
rl = ResponseLink.create(source: source, response: request, creator_id: helpers.current_person&.id)
if rl.persisted?
# mark_source_matched is handled in model callback
else
Rails.logger.error("Failed to create ResponseLink: #{rl.errors.full_messages.join(', ')}")
end
redirect_to joatu_request_path(request), notice: 'Request created in response to offer.'
else
redirect_back fallback_location: joatu_offer_path(source),
alert: request.errors.full_messages.to_sentence
end
elsif source.is_a?(BetterTogether::Joatu::Request)
offer = BetterTogether::Joatu::Offer.new
offer.name = source.name
offer.description = source.description
offer.creator_id = helpers.current_person&.id
offer.target_type = source.target_type if source.respond_to?(:target_type)
offer.target_id = source.target_id if source.respond_to?(:target_id)
offer.urgency = source.urgency if source.respond_to?(:urgency)
offer.address_id = source.address_id if source.respond_to?(:address_id)
offer.category_ids = source.categories.pluck(:id) if source.respond_to?(:categories)

if offer.save
rl = ResponseLink.create(source: source, response: offer, creator_id: helpers.current_person&.id)
unless rl.persisted?
Rails.logger.error("Failed to create ResponseLink: #{rl.errors.full_messages.join(', ')}")
end
redirect_to joatu_offer_path(offer), notice: 'Offer created in response to request.'
else
redirect_back fallback_location: joatu_request_path(source),
alert: offer.errors.full_messages.to_sentence
end
else
redirect_back fallback_location: joatu_hub_path, alert: 'Unsupported source type'
end
rescue StandardError => e
# Log full backtrace to surface errors during tests
Rails.logger.error(
"ResponseLinksController#create failed: #{e.class} - #{e.message}\n#{e.backtrace.join("\n")}"
)
raise
end
end
end
end
Loading
Loading