diff --git a/Gemfile b/Gemfile
index 960b25729..d08ba4763 100644
--- a/Gemfile
+++ b/Gemfile
@@ -104,10 +104,10 @@ gem "ransack"
gem "rails-controller-testing"
# Use Action Policy for authorization framework
-gem "action_policy", "~> 0.7.2"
+gem "action_policy", "~> 0.7.3"
# Use ViewComponent for our presenter pattern framework
-gem "view_component", "~> 3.20"
+gem "view_component", "~> 3.21"
# Use dry-types for defining types
gem "dry-types", "~> 1.7"
diff --git a/Gemfile.lock b/Gemfile.lock
index 5b21fb911..dcbf0bcfa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
- action_policy (0.7.2)
+ action_policy (0.7.3)
ruby-next-core (>= 1.0)
actioncable (8.0.1)
actionpack (= 8.0.1)
@@ -146,7 +146,7 @@ GEM
railties (>= 6.0.0)
sass-embedded (~> 1.63)
date (3.4.1)
- debug (1.9.2)
+ debug (1.10.0)
irb (~> 1.10)
reline (>= 0.3.8)
devise (4.9.4)
@@ -277,7 +277,7 @@ GEM
activesupport (>= 6.0.0)
railties (>= 6.0.0)
io-console (0.8.0)
- irb (1.14.2)
+ irb (1.14.3)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jbuilder (2.13.0)
@@ -310,7 +310,7 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
- logger (1.6.3)
+ logger (1.6.4)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -439,7 +439,7 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
- rdoc (6.9.1)
+ rdoc (6.10.0)
psych (>= 4.0.0)
regexp_parser (2.9.3)
reline (0.6.0)
@@ -542,7 +542,7 @@ GEM
unicode-emoji (4.0.4)
uri (1.0.2)
useragent (0.16.11)
- view_component (3.20.0)
+ view_component (3.21.0)
activesupport (>= 5.2.0, < 8.1)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
@@ -570,7 +570,7 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
- action_policy (~> 0.7.2)
+ action_policy (~> 0.7.3)
active_link_to
active_storage_validations
acts_as_tenant
@@ -625,7 +625,7 @@ DEPENDENCIES
traceroute
turbo-rails
tzinfo-data
- view_component (~> 3.20)
+ view_component (~> 3.21)
web-console
RUBY VERSION
diff --git a/app/controllers/contacts_controller.rb b/app/controllers/contacts_controller.rb
index 9e60c609b..9725ea9fa 100644
--- a/app/controllers/contacts_controller.rb
+++ b/app/controllers/contacts_controller.rb
@@ -4,6 +4,7 @@ class ContactsController < ApplicationController
def new
@contact = Contact.new
+ @contact.email = current_user.email if current_user.present?
end
def create
diff --git a/app/controllers/feedback_controller.rb b/app/controllers/feedback_controller.rb
index c03ecac77..749e75fd3 100644
--- a/app/controllers/feedback_controller.rb
+++ b/app/controllers/feedback_controller.rb
@@ -7,6 +7,7 @@ class FeedbackController < ApplicationController
def new
@feedback = Feedback.new
+ @feedback.email = current_user.email if current_user.present?
end
def create
diff --git a/app/controllers/organizations/staff/external_form_upload_controller.rb b/app/controllers/organizations/staff/external_form_upload_controller.rb
index 0e209ab92..6e759f42f 100644
--- a/app/controllers/organizations/staff/external_form_upload_controller.rb
+++ b/app/controllers/organizations/staff/external_form_upload_controller.rb
@@ -11,8 +11,15 @@ def index
def create
authorize! :external_form_upload, context: {organization: Current.organization}
- import = Organizations::Importers::CsvImportService.new(params[:files]).call
- render turbo_stream: turbo_stream.replace("results", partial: "organizations/staff/external_form_upload/upload_results", locals: {import: import})
+ file = params[:files]
+ @blob = ActiveStorage::Blob.create_and_upload!(io: file, filename: file.original_filename)
+
+ CsvImportJob.perform_later(@blob.signed_id, current_user.id)
+
+ flash.now[:notice] = t(".processing_file")
+ respond_to do |format|
+ format.turbo_stream
+ end
end
end
end
diff --git a/app/jobs/csv_import_job.rb b/app/jobs/csv_import_job.rb
new file mode 100644
index 000000000..1a97e0128
--- /dev/null
+++ b/app/jobs/csv_import_job.rb
@@ -0,0 +1,11 @@
+class CsvImportJob < ApplicationJob
+ queue_as :default
+
+ def perform(blob_signed_id, current_user_id)
+ blob = ActiveStorage::Blob.find_signed(blob_signed_id)
+
+ Organizations::Importers::CsvImportService.new(blob, current_user_id).call
+ ensure
+ blob.purge_later
+ end
+end
diff --git a/app/models/contact.rb b/app/models/contact.rb
index 091b5b7cd..05eb07764 100644
--- a/app/models/contact.rb
+++ b/app/models/contact.rb
@@ -2,7 +2,8 @@ class Contact
include ActiveModel::Model
attr_accessor :name, :email, :message
- validates :name, :email, :message, presence: true
+ validates :name, :email, presence: true
+ validates :message, presence: true, length: {maximum: 500}
# credit: https://medium.com/@limichelle21/building-and-debugging-a-contact-form-with-rails-mailgun-heroku-c0185b8bf419
end
diff --git a/app/models/organization_account_request.rb b/app/models/organization_account_request.rb
index 1d1eb659c..85b64b116 100644
--- a/app/models/organization_account_request.rb
+++ b/app/models/organization_account_request.rb
@@ -5,7 +5,7 @@ class OrganizationAccountRequest
before_validation :normalize_phone
- validates :name, :email, :city_town, :country, :province_state, presence: true
+ validates :name, :email, :requester_name, :city_town, :country, :province_state, presence: true
validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
validates :phone_number, presence: true, phone: {possible: true, allow_blank: true}
diff --git a/app/services/organizations/create_service.rb b/app/services/organizations/create_service.rb
index 8a4f2ebfe..599bba598 100644
--- a/app/services/organizations/create_service.rb
+++ b/app/services/organizations/create_service.rb
@@ -1,11 +1,12 @@
-# class to create a new location, organization, user, and staff account with role admin
-# email is sent to admin user if all steps are successful
+# Class to create a new location, organization, user, and staff account with role admin.
+# An email is sent to admin user if all steps are successful.
+# Be sure to use the Country and State codes from countries_states.yml
# call with Organizations::CreateService.new.signal(args)
# sample args:
# {
# location: {
-# country: 'Mexico',
-# city_town: 'La Ventana',
+# country: 'MX',
+# city_town: 'JAL',
# province_state: 'Baja'
# },
# organization: {
diff --git a/app/services/organizations/importers/csv_import_service.rb b/app/services/organizations/importers/csv_import_service.rb
index 54d78b32f..da0faf5af 100644
--- a/app/services/organizations/importers/csv_import_service.rb
+++ b/app/services/organizations/importers/csv_import_service.rb
@@ -4,9 +4,10 @@ module Organizations
module Importers
class CsvImportService
Status = Data.define(:success?, :count, :no_match, :errors)
- def initialize(file)
+ def initialize(file, current_user_id)
@file = file
- @organization = Current.organization
+ @organization = ActsAsTenant.current_tenant
+ @current_user = User.find(current_user_id)
@count = 0
@no_match = []
@errors = []
@@ -14,51 +15,55 @@ def initialize(file)
def call
catch(:halt_import) do
- validate_file
-
- CSV.foreach(@file.to_path, headers: true, skip_blanks: true).with_index(2) do |row, index|
- # Header may be different depending on which form applicaiton was used(e.g. google forms) or how it was created(User creates form with "Email Address")
- email = row[@email_header].downcase
- # Google forms uses "Timestamp", other services may use a different header
- csv_timestamp = Time.parse(row["Timestamp"]) if row["Timestamp"].present?
-
- person = Person.find_by(email:, organization: @organization)
- previously_matched_form_submission = FormSubmission.where(person:, csv_timestamp:)
-
- if person.nil?
- @no_match << [index, email]
- elsif previously_matched_form_submission.present?
- next
- else
- ActiveRecord::Base.transaction do
- create_form_answers(FormSubmission.create!(person:, csv_timestamp:), row)
- @count += 1
+ @file.download do |data|
+ validate_file(data)
+ CSV.parse(data, headers: true, skip_blanks: true).each_with_index do |row, index|
+ # Header may be different depending on which form applicaiton was used(e.g. google forms) or how it was created(User creates form with "Email Address")
+ email = row[@email_header].downcase
+ # Google forms uses "Timestamp", other services may use a different header
+ csv_timestamp = Time.parse(row["Timestamp"]) if row["Timestamp"].present?
+
+ person = Person.find_by(email:, organization: @organization)
+ previously_matched_form_submission = FormSubmission.where(person:, csv_timestamp:)
+
+ if person.nil?
+ @no_match << [index + 2, email]
+ elsif previously_matched_form_submission.present?
+ next
+ else
+ ActiveRecord::Base.transaction do
+ create_form_answers(FormSubmission.create!(person:, csv_timestamp:), row)
+ @count += 1
+ end
end
+ rescue => e
+ @errors << [index + 2, e]
end
- rescue => e
- @errors << [index, e]
end
end
- Status.new(@errors.empty?, @count, @no_match, @errors)
+ Turbo::StreamsChannel.broadcast_replace_to ["csv_import", @current_user],
+ target: "results",
+ partial: "organizations/staff/external_form_upload/upload_results",
+ locals: {
+ import: Status.new(@errors.empty?, @count, @no_match, @errors)
+ }
end
private
- def validate_file
+ def validate_file(data)
raise FileTypeError unless @file.content_type == "text/csv"
-
- first_row = CSV.foreach(@file.to_path).first
- raise FileEmptyError if first_row.nil?
+ first_row = CSV.new(data).shift
raise TimestampColumnError unless first_row.include?("Timestamp")
email_headers = ["Email", "email", "Email Address", "email address"]
- email_headers.each do |e|
- @email_header = e if first_row.include?(e)
+ email_headers.each do |header|
+ @email_header = header if first_row.include?(header)
end
raise EmailColumnError unless @email_header
- rescue FileTypeError, FileEmptyError, TimestampColumnError, EmailColumnError => e
- @errors << e
+ rescue FileTypeError, TimestampColumnError, EmailColumnError => e
+ @errors << [1, e]
throw :halt_import
end
diff --git a/app/views/layouts/adopter_foster_dashboard.html.erb b/app/views/layouts/adopter_foster_dashboard.html.erb
index bb4858c06..e6eaca4b3 100644
--- a/app/views/layouts/adopter_foster_dashboard.html.erb
+++ b/app/views/layouts/adopter_foster_dashboard.html.erb
@@ -121,11 +121,6 @@
Log Out
<% end %>
-
- <%= active_link_to adopter_fosterer_dashboard_index_path, class: "nav-link" do %>
- Delete Account
- <% end %>
-
diff --git a/app/views/layouts/shared/_navbar.html.erb b/app/views/layouts/shared/_navbar.html.erb
index 3352787e7..8c4be241c 100644
--- a/app/views/layouts/shared/_navbar.html.erb
+++ b/app/views/layouts/shared/_navbar.html.erb
@@ -1,15 +1,5 @@