hello rubocop

This commit is contained in:
bumi 2020-04-28 01:40:06 +02:00
parent 2e5954124d
commit fa348cdeeb
69 changed files with 382 additions and 202 deletions

32
.rubocop.yml Normal file
View File

@ -0,0 +1,32 @@
# The behavior of RuboCop can be controlled via the .rubocop.yml
# configuration file. It makes it possible to enable/disable
# certain cops (checks) and to alter their behavior if they accept
# any parameters. The file can be placed either in your home
# directory or in some project directory.
#
# RuboCop will start looking for the configuration file in the directory
# where the inspected file is and continue its way up to the root directory.
#
# See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md
#
require:
- rubocop-rails
- rubocop-rspec
AllCops:
NewCops: enable
Exclude:
- 'bin/*'
- 'db/migrate/*'
- 'db/schema.rb'
- 'vendor/bundle/**/*'
- 'node_modules/**/*'
Layout/LineLength:
AllowHeredoc: true
Max: 150
Style/Documentation:
Enabled: false

17
Gemfile
View File

@ -1,15 +1,17 @@
# frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.1' ruby '2.6.1'
gem 'rails' gem 'jbuilder'
gem 'pg' gem 'pg'
gem 'puma' gem 'puma'
gem 'rails'
gem 'sass-rails' gem 'sass-rails'
gem 'webpacker'
gem 'turbolinks' gem 'turbolinks'
gem 'jbuilder' gem 'webpacker'
# Use Redis adapter to run Action Cable in production # Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0' # gem 'redis', '~> 4.0'
# Use Active Model has_secure_password # Use Active Model has_secure_password
@ -23,8 +25,8 @@ gem 'bootsnap', '>= 1.4.2', require: false
gem 'lockbox' gem 'lockbox'
gem 'aws-sdk-s3', require: false
gem 'airrecord' gem 'airrecord'
gem 'aws-sdk-s3', require: false
gem 'google-api-client' gem 'google-api-client'
gem 'rack-cors' gem 'rack-cors'
gem 'sentry-raven' gem 'sentry-raven'
@ -33,8 +35,11 @@ gem 'sorcery'
group :development, :test do group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console # Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'byebug', platforms: %i[mri mingw x64_mingw]
gem 'dotenv-rails' gem 'dotenv-rails'
gem 'rubocop', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
end end
group :development do group :development do
@ -56,4 +61,4 @@ group :test do
end end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]

View File

@ -61,6 +61,7 @@ GEM
airrecord (1.0.5) airrecord (1.0.5)
faraday (>= 0.10, < 2.0) faraday (>= 0.10, < 2.0)
net-http-persistent (>= 2.9) net-http-persistent (>= 2.9)
ast (2.4.0)
aws-eventstream (1.0.3) aws-eventstream (1.0.3)
aws-partitions (1.263.0) aws-partitions (1.263.0)
aws-sdk-core (3.89.1) aws-sdk-core (3.89.1)
@ -115,6 +116,7 @@ GEM
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.8.2) i18n (1.8.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jaro_winkler (1.5.4)
jbuilder (2.10.0) jbuilder (2.10.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
jmespath (1.4.0) jmespath (1.4.0)
@ -153,6 +155,9 @@ GEM
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
os (1.1.0) os (1.1.0)
parallel (1.19.1)
parser (2.7.1.1)
ast (~> 2.4.0)
pg (1.2.3) pg (1.2.3)
public_suffix (4.0.4) public_suffix (4.0.4)
puma (4.3.3) puma (4.3.3)
@ -190,6 +195,7 @@ GEM
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
rainbow (3.0.0)
rake (13.0.1) rake (13.0.1)
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.10.1) rb-inotify (0.10.1)
@ -199,6 +205,22 @@ GEM
declarative-option (< 0.2.0) declarative-option (< 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
retriable (3.1.2) retriable (3.1.2)
rexml (3.2.4)
rubocop (0.82.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
rexml
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-rails (2.5.2)
activesupport
rack (>= 1.1)
rubocop (>= 0.72.0)
rubocop-rspec (1.38.1)
rubocop (>= 0.68.1)
ruby-progressbar (1.10.1)
sass-rails (6.0.0) sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1) sassc-rails (~> 2.1, >= 2.1.1)
sassc (2.2.1) sassc (2.2.1)
@ -245,6 +267,7 @@ GEM
tzinfo (1.2.7) tzinfo (1.2.7)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uber (0.1.0) uber (0.1.0)
unicode-display_width (1.7.0)
webpacker (5.0.1) webpacker (5.0.1)
activesupport (>= 5.2) activesupport (>= 5.2)
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
@ -272,6 +295,9 @@ DEPENDENCIES
puma puma
rack-cors rack-cors
rails rails
rubocop
rubocop-rails
rubocop-rspec
sass-rails sass-rails
sentry-raven sentry-raven
sequenced sequenced

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module ApplicationCable module ApplicationCable
class Channel < ActionCable::Channel::Base class Channel < ActionCable::Channel::Base
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module ApplicationCable module ApplicationCable
class Connection < ActionCable::Connection::Base class Connection < ActionCable::Connection::Base
end end

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
helper_method :current_user, :logged_in? helper_method :current_user, :logged_in?
def require_login def require_login
redirect_to login_url unless current_user.present? redirect_to login_url if current_user.blank?
end end
def current_user def current_user

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class FileUploadsController < ApplicationController class FileUploadsController < ApplicationController
def show def show
@form = Form.find_by!(token: params[:form_id]) @form = Form.find_by!(token: params[:form_id])

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'google/apis/sheets_v4' require 'google/apis/sheets_v4'
require 'google/api_client/client_secrets' require 'google/api_client/client_secrets'
class FormsController < ApplicationController class FormsController < ApplicationController

View File

@ -1,5 +1,6 @@
class HomeController < ApplicationController # frozen_string_literal: true
class HomeController < ApplicationController
def index def index
redirect_to forms_url if logged_in? redirect_to forms_url if logged_in?
end end

View File

@ -1,5 +1,6 @@
class OauthsController < ApplicationController # frozen_string_literal: true
class OauthsController < ApplicationController
# Sends the user on a trip to the provider, # Sends the user on a trip to the provider,
# and after authorizing there back to the callback url. # and after authorizing there back to the callback url.
def oauth def oauth
@ -9,26 +10,25 @@ class OauthsController < ApplicationController
def callback def callback
provider = params[:provider] provider = params[:provider]
if @user = login_from(provider) if @user = login_from(provider)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!" redirect_to root_path, notice: "Logged in from #{provider.titleize}!"
else else
begin begin
@user = create_from(provider) @user = create_from(provider)
if authentication = @user.authentications.find_by(provider: provider) if authentication = @user.authentications.find_by(provider: provider)
authentication.update({ authentication.update({
access_token: @access_token.token, access_token: @access_token.token,
refresh_token: @access_token.refresh_token, refresh_token: @access_token.refresh_token,
expires_at: Time.at(@access_token.expires_at) expires_at: Time.zone.at(@access_token.expires_at)
}) })
end end
reset_session reset_session
auto_login(@user) auto_login(@user)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!" redirect_to root_path, notice: "Logged in from #{provider.titleize}!"
rescue rescue StandardError
Rails.logger.error("Failed to login from #{provider}") Rails.logger.error("Failed to login from #{provider}")
redirect_to root_path, :alert => "Failed to login from #{provider.titleize}!" redirect_to root_path, alert: "Failed to login from #{provider.titleize}!"
end end
end end
end end
end end

View File

@ -1,5 +1,6 @@
class SessionsController < ApplicationController # frozen_string_literal: true
class SessionsController < ApplicationController
def new def new
reset_session reset_session
end end
@ -8,5 +9,4 @@ class SessionsController < ApplicationController
reset_session reset_session
redirect_to root_url redirect_to root_url
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'google/apis/sheets_v4' require 'google/apis/sheets_v4'
class SubmissionsController < ApplicationController class SubmissionsController < ApplicationController
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
@ -14,10 +16,14 @@ class SubmissionsController < ApplicationController
respond_to do |format| respond_to do |format|
if @submission.save if @submission.save
format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? } format.html do
redirect_to(@form.thank_you_url) if @form.thank_you_url.present?
end
format.json { render(json: { success: true, submission: @submission.data }) } format.json { render(json: { success: true, submission: @submission.data }) }
else else
format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? } format.html do
redirect_to(@form.thank_you_url) if @form.thank_you_url.present?
end
format.json { render(json: { error: @submission.errors }, status: 422) } format.json { render(json: { error: @submission.errors }, status: 422) }
end end
end end

View File

@ -1,2 +1,4 @@
# frozen_string_literal: true
module ApplicationHelper module ApplicationHelper
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationJob < ActiveJob::Base class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock # Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked # retry_on ActiveRecord::Deadlocked

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
class SubmissionAppendJob < ApplicationJob class SubmissionAppendJob < ApplicationJob
queue_as :default queue_as :default
rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |exception| rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |_exception|
submission_id = self.arguments.first submission_id = arguments.first
Rails.logger.error("AuthorizationError during SubmissionAppend: submission_id=#{submission_id}") Rails.logger.error("AuthorizationError during SubmissionAppend: submission_id=#{submission_id}")
submission = Submission.find(submission_id) submission = Submission.find(submission_id)
submission.form.deactivate!('AuthorizationError') submission.form.deactivate!('AuthorizationError')

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com' default from: 'from@example.com'
layout 'mailer' layout 'mailer'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true self.abstract_class = true
end end

View File

@ -1,7 +1,9 @@
# frozen_string_literal: true
class Authentication < ApplicationRecord class Authentication < ApplicationRecord
belongs_to :user belongs_to :user
scope :for, -> (provider) { where(provider: provider) } scope :for, ->(provider) { where(provider: provider) }
encrypts :access_token encrypts :access_token
encrypts :refresh_token encrypts :refresh_token
@ -12,17 +14,17 @@ class Authentication < ApplicationRecord
def google_authorization def google_authorization
return nil unless provider == 'google' return nil unless provider == 'google'
@google_authorization ||= CLIENT_SECRETS.to_authorization.tap do |c| @google_authorization ||= CLIENT_SECRETS.to_authorization.tap do |c|
c.access_token = self.access_token c.access_token = access_token
c.refresh_token = self.refresh_token c.refresh_token = refresh_token
c.expires_at = self.expires_at c.expires_at = expires_at
if expires_at < 1.minute.from_now if expires_at < 1.minute.from_now
c.refresh! c.refresh!
self.access_token = c.access_token if c.access_token.present? self.access_token = c.access_token if c.access_token.present?
self.refresh_token = c.refresh_token if c.refresh_token.present? self.refresh_token = c.refresh_token if c.refresh_token.present?
self.expires_at = Time.at(c.expires_at) if c.expires_at.present? self.expires_at = Time.zone.at(c.expires_at) if c.expires_at.present?
end end
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class Form < ApplicationRecord class Form < ApplicationRecord
belongs_to :user belongs_to :user
has_many :submissions, dependent: :destroy has_many :submissions, dependent: :destroy
@ -10,12 +12,12 @@ class Form < ApplicationRecord
encrypts :airtable_api_key encrypts :airtable_api_key
encrypts :airtable_app_key encrypts :airtable_app_key
validates_presence_of :title validates :title, presence: true
validates_inclusion_of :backend_name, in: ['google_sheets', 'airtable'] validates :backend_name, inclusion: { in: %w[google_sheets airtable] }
# Airtable validations # Airtable validations
validates_presence_of :airtable_api_key, if: :airtable? validates :airtable_api_key, presence: { if: :airtable? }
validates_presence_of :airtable_app_key, if: :airtable? validates :airtable_app_key, presence: { if: :airtable? }
validates_presence_of :airtable_table, if: :airtable? validates :airtable_table, presence: { if: :airtable? }
# TODO: use counter_cache option on association # TODO: use counter_cache option on association
def submissions_count def submissions_count
@ -27,12 +29,10 @@ class Form < ApplicationRecord
end end
def deactivate!(reason = nil) def deactivate!(reason = nil)
self.user.deactivate!(reason) user.deactivate!(reason)
end end
def active? delegate :active?, to: :user
self.user.active?
end
def airtable? def airtable?
backend_name == 'airtable' backend_name == 'airtable'

View File

@ -1,10 +1,12 @@
# frozen_string_literal: true
class Submission < ApplicationRecord class Submission < ApplicationRecord
belongs_to :form belongs_to :form
has_many_attached :files has_many_attached :files
acts_as_sequenced scope: :form_id acts_as_sequenced scope: :form_id
validates_presence_of :data, if: :appended_at? validates :data, presence: { if: :appended_at? }
def process_data(submitted_data) def process_data(submitted_data)
processed_data = {} processed_data = {}
@ -12,7 +14,7 @@ class Submission < ApplicationRecord
processed_data[key] = submission_value_for(value) processed_data[key] = submission_value_for(value)
end end
update_attribute(:data, processed_data) update_attribute(:data, processed_data)
SubmissionAppendJob.perform_later(self.id) SubmissionAppendJob.perform_later(id)
end end
def submission_value_for(value) def submission_value_for(value)
@ -37,7 +39,8 @@ class Submission < ApplicationRecord
attachment = ActiveStorage::Attachment.new(record: self, name: 'files', blob: create_one.blob) attachment = ActiveStorage::Attachment.new(record: self, name: 'files', blob: create_one.blob)
attachment.save attachment.save
# return the URL that we use to show in the Spreadsheet # return the URL that we use to show in the Spreadsheet
Rails.application.routes.url_helpers.file_upload_url(form_id: form, submission_id: self, id: attachment.token, host: DEFAULT_HOST, filename: attachment.blob.filename) Rails.application.routes.url_helpers.file_upload_url(form_id: form, submission_id: self, id: attachment.token,
host: DEFAULT_HOST, filename: attachment.blob.filename)
else else
value.to_s value.to_s
end end

View File

@ -1,9 +1,11 @@
# frozen_string_literal: true
class User < ApplicationRecord class User < ApplicationRecord
authenticates_with_sorcery! authenticates_with_sorcery!
has_many :authentications, dependent: :destroy has_many :authentications, dependent: :destroy
has_many :forms, dependent: :destroy has_many :forms, dependent: :destroy
def deactivate!(reason = nil) def deactivate!(_reason = nil)
# currently we only use deactivate if we get an authentication exception appending data to a spreadsheet # currently we only use deactivate if we get an authentication exception appending data to a spreadsheet
authentications.last&.update(expires_at: Time.current) authentications.last&.update(expires_at: Time.current)
end end

View File

@ -8,46 +8,48 @@
# this file is here to facilitate running it. # this file is here to facilitate running it.
# #
require "rubygems" require 'rubygems'
m = Module.new do m = Module.new do
module_function module_function
def invoked_as_script? def invoked_as_script?
File.expand_path($0) == File.expand_path(__FILE__) File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__)
end end
def env_var_version def env_var_version
ENV["BUNDLER_VERSION"] ENV['BUNDLER_VERSION']
end end
def cli_arg_version def cli_arg_version
return unless invoked_as_script? # don't want to hijack other binstubs return unless invoked_as_script? # don't want to hijack other binstubs
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` unless 'update'.start_with?(ARGV.first || ' ')
return
end # must be running `bundle update`
bundler_version = nil bundler_version = nil
update_index = nil update_index = nil
ARGV.each_with_index do |a, i| ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
bundler_version = $1
bundler_version = Regexp.last_match(1)
update_index = i update_index = i
end end
bundler_version bundler_version
end end
def gemfile def gemfile
gemfile = ENV["BUNDLE_GEMFILE"] gemfile = ENV['BUNDLE_GEMFILE']
return gemfile if gemfile && !gemfile.empty? return gemfile if gemfile && !gemfile.empty?
File.expand_path("../../Gemfile", __FILE__) File.expand_path('../Gemfile', __dir__)
end end
def lockfile def lockfile
lockfile = lockfile =
case File.basename(gemfile) case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile)
else "#{gemfile}.lock" else "#{gemfile}.lock"
end end
File.expand_path(lockfile) File.expand_path(lockfile)
@ -55,15 +57,17 @@ m = Module.new do
def lockfile_version def lockfile_version
return unless File.file?(lockfile) return unless File.file?(lockfile)
lockfile_contents = File.read(lockfile) lockfile_contents = File.read(lockfile)
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
Regexp.last_match(1) Regexp.last_match(1)
end end
def bundler_version def bundler_version
@bundler_version ||= @bundler_version ||=
env_var_version || cli_arg_version || env_var_version || cli_arg_version ||
lockfile_version lockfile_version
end end
def bundler_requirement def bundler_requirement
@ -73,28 +77,30 @@ m = Module.new do
requirement = bundler_gem_version.approximate_recommendation requirement = bundler_gem_version.approximate_recommendation
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.7.0')
requirement += ".a" if bundler_gem_version.prerelease? requirement += '.a' if bundler_gem_version.prerelease?
requirement requirement
end end
def load_bundler! def load_bundler!
ENV["BUNDLE_GEMFILE"] ||= gemfile ENV['BUNDLE_GEMFILE'] ||= gemfile
activate_bundler activate_bundler
end end
def activate_bundler def activate_bundler
gem_error = activation_error_handling do gem_error = activation_error_handling do
gem "bundler", bundler_requirement gem 'bundler', bundler_requirement
end end
return if gem_error.nil? return if gem_error.nil?
require_error = activation_error_handling do require_error = activation_error_handling do
require "bundler/version" require 'bundler/version'
end end
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
exit 42 exit 42
end end
@ -109,6 +115,4 @@ end
m.load_bundler! m.load_bundler!
if m.invoked_as_script? load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script?
load Gem.bin_path("bundler", "bundle")
end

View File

@ -1,6 +1,8 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
begin begin
load File.expand_path('../spring', __FILE__) load File.expand_path('spring', __dir__)
rescue LoadError => e rescue LoadError => e
raise unless e.message.include?('spring') raise unless e.message.include?('spring')
end end

View File

@ -1,6 +1,8 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
begin begin
load File.expand_path('../spring', __FILE__) load File.expand_path('spring', __dir__)
rescue LoadError => e rescue LoadError => e
raise unless e.message.include?('spring') raise unless e.message.include?('spring')
end end

View File

@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
require 'fileutils' require 'fileutils'
# path to your application root. # path to your application root.

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
# This file loads Spring without using Bundler, in order to be fast. # This file loads Spring without using Bundler, in order to be fast.
# It gets overwritten when you run the `spring binstub` command. # It gets overwritten when you run the `spring binstub` command.

View File

@ -1,18 +1,19 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" ENV['RAILS_ENV'] ||= ENV['RACK_ENV'] || 'development'
ENV["NODE_ENV"] ||= "development" ENV['NODE_ENV'] ||= 'development'
require "pathname" require 'pathname'
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
Pathname.new(__FILE__).realpath) Pathname.new(__FILE__).realpath)
require "bundler/setup" require 'bundler/setup'
require "webpacker" require 'webpacker'
require "webpacker/webpack_runner" require 'webpacker/webpack_runner'
APP_ROOT = File.expand_path("..", __dir__) APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do Dir.chdir(APP_ROOT) do
Webpacker::WebpackRunner.run(ARGV) Webpacker::WebpackRunner.run(ARGV)
end end

View File

@ -1,18 +1,19 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" ENV['RAILS_ENV'] ||= ENV['RACK_ENV'] || 'development'
ENV["NODE_ENV"] ||= "development" ENV['NODE_ENV'] ||= 'development'
require "pathname" require 'pathname'
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
Pathname.new(__FILE__).realpath) Pathname.new(__FILE__).realpath)
require "bundler/setup" require 'bundler/setup'
require "webpacker" require 'webpacker'
require "webpacker/dev_server_runner" require 'webpacker/dev_server_runner'
APP_ROOT = File.expand_path("..", __dir__) APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do Dir.chdir(APP_ROOT) do
Webpacker::DevServerRunner.run(ARGV) Webpacker::DevServerRunner.run(ARGV)
end end

View File

@ -1,11 +1,11 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
APP_ROOT = File.expand_path('..', __dir__) APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do Dir.chdir(APP_ROOT) do
begin exec 'yarnpkg', *ARGV
exec "yarnpkg", *ARGV rescue Errno::ENOENT
rescue Errno::ENOENT warn 'Yarn executable was not detected in the system.'
$stderr.puts "Yarn executable was not detected in the system." warn 'Download Yarn at https://yarnpkg.com/en/docs/install'
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" exit 1
exit 1
end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# This file is used by Rack-based servers to start the application. # This file is used by Rack-based servers to start the application.
require_relative 'config/environment' require_relative 'config/environment'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative 'boot' require_relative 'boot'
require 'rails/all' require 'rails/all'
@ -29,7 +31,7 @@ module Tinyform
config.middleware.insert_before 0, Rack::Cors do config.middleware.insert_before 0, Rack::Cors do
allow do allow do
origins '*' origins '*'
resource '/s/*', headers: :any, methods: [:post, :put, :options] resource '/s/*', headers: :any, methods: %i[post put options]
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile. require 'bundler/setup' # Set up gems listed in the Gemfile.

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Load the Rails application. # Load the Rails application.
require_relative 'application' require_relative 'application'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
@ -14,7 +16,7 @@ Rails.application.configure do
# Enable/disable caching. By default caching is disabled. # Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching. # Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist? if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true config.action_controller.enable_fragment_cache_logging = true

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
@ -51,7 +53,7 @@ Rails.application.configure do
config.log_level = :info config.log_level = :info
# Prepend all log lines with the following tags. # Prepend all log lines with the following tags.
config.log_tags = [ :request_id ] config.log_tags = [:request_id]
# Use a different cache store in production. # Use a different cache store in production.
# config.cache_store = :mem_cache_store # config.cache_store = :mem_cache_store
@ -80,7 +82,7 @@ Rails.application.configure do
# require 'syslog/logger' # require 'syslog/logger'
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV["RAILS_LOG_TO_STDOUT"].present? if ENV['RAILS_LOG_TO_STDOUT'].present?
logger = ActiveSupport::Logger.new(STDOUT) logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger) config.logger = ActiveSupport::TaggedLogging.new(logger)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# The test environment is used exclusively to run your application's # The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that # test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped # your test database is "scratch space" for the test suite and is wiped

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# ActiveSupport::Reloader.to_prepare do # ActiveSupport::Reloader.to_prepare do

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets. # Version of your assets, change this if you want to expire all your assets.

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.configuration.to_prepare do Rails.configuration.to_prepare do
ActiveStorage::Attachment.send(:has_secure_token) ActiveStorage::Attachment.send(:has_secure_token)
end end

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Define an application-wide content security policy # Define an application-wide content security policy

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Specify a serializer for the signed and encrypted cookie jars. # Specify a serializer for the signed and encrypted cookie jars.

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
GOOGLE_DEMO_FORM = Form.find_by(id: ENV['GOOGLE_DEMO_FORM_ID']) GOOGLE_DEMO_FORM = Form.find_by(id: ENV['GOOGLE_DEMO_FORM_ID'])
AIRTABLE_DEMO_FORM = Form.find_by(id: ENV['AIRTABLE_DEMO_FORM_ID']) AIRTABLE_DEMO_FORM = Form.find_by(id: ENV['AIRTABLE_DEMO_FORM_ID'])
AIRTABLE_DEMO_EMBED_URL = ENV['AIRTABLE_DEMO_EMBED_URL'] AIRTABLE_DEMO_EMBED_URL = ENV['AIRTABLE_DEMO_EMBED_URL']

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file. # Configure sensitive parameters which will be filtered from the log file.

View File

@ -1,13 +1,15 @@
# frozen_string_literal: true
require 'google/api_client/client_secrets' require 'google/api_client/client_secrets'
require 'google/apis' require 'google/apis'
secrets_options = { secrets_options = {
"client_id" => ENV['GOOGLE_CLIENT_ID'], 'client_id' => ENV['GOOGLE_CLIENT_ID'],
"project_id" => ENV['GOOGLE_PROJECT_ID'], 'project_id' => ENV['GOOGLE_PROJECT_ID'],
"client_secret" => ENV['GOOGLE_CLIENT_SECRET'], 'client_secret' => ENV['GOOGLE_CLIENT_SECRET'],
"auth_uri" => "https://accounts.google.com/o/oauth2/auth", 'auth_uri' => 'https://accounts.google.com/o/oauth2/auth',
"token_uri" => "https://oauth2.googleapis.com/token", 'token_uri' => 'https://oauth2.googleapis.com/token',
"auth_provider_x509_cert_url" => "https://www.googleapis.com/oauth2/v1/certs" 'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs'
} }
CLIENT_SECRETS = Google::APIClient::ClientSecrets.new("web" => secrets_options) CLIENT_SECRETS = Google::APIClient::ClientSecrets.new('web' => secrets_options)
Google::Apis.logger = ::Rails.logger Google::Apis.logger = ::Rails.logger

View File

@ -1 +1,3 @@
# frozen_string_literal: true
DEFAULT_HOST = ENV['DEFAULT_HOST'] || 'localhost:3000' DEFAULT_HOST = ENV['DEFAULT_HOST'] || 'localhost:3000'

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections # Add new inflection rules using the following format. Inflections

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks: # Add new mime types for use in respond_to blocks:

View File

@ -1,10 +1,12 @@
# frozen_string_literal: true
# The first thing you need to configure is which modules you need in your app. # The first thing you need to configure is which modules you need in your app.
# The default is nothing which will include only core features (password encryption, login/logout). # The default is nothing which will include only core features (password encryption, login/logout).
# #
# Available submodules are: :user_activation, :http_basic_auth, :remember_me, # Available submodules are: :user_activation, :http_basic_auth, :remember_me,
# :reset_password, :session_timeout, :brute_force_protection, :activity_logging, # :reset_password, :session_timeout, :brute_force_protection, :activity_logging,
# :magic_login, :external # :magic_login, :external
Rails.application.config.sorcery.submodules = [:reset_password, :external, :magic_login] Rails.application.config.sorcery.submodules = %i[reset_password external magic_login]
# Here you can configure each submodule's features. # Here you can configure each submodule's features.
Rails.application.config.sorcery.configure do |config| Rails.application.config.sorcery.configure do |config|
@ -152,9 +154,9 @@ Rails.application.config.sorcery.configure do |config|
# #
config.google.key = ENV['GOOGLE_CLIENT_ID'] config.google.key = ENV['GOOGLE_CLIENT_ID']
config.google.secret = ENV['GOOGLE_CLIENT_SECRET'] config.google.secret = ENV['GOOGLE_CLIENT_SECRET']
config.google.callback_url = (ENV['GOOGLE_AUTH_CALLBACK_URL'] || "http://localhost:3000/oauth/callback?provider=google") config.google.callback_url = (ENV['GOOGLE_AUTH_CALLBACK_URL'] || 'http://localhost:3000/oauth/callback?provider=google')
config.google.user_info_mapping = {:email => "email", :name => "name", :google_id => "id"} config.google.user_info_mapping = { email: 'email', name: 'name', google_id: 'id' }
config.google.scope = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/spreadsheets" config.google.scope = 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/spreadsheets'
config.google.auth_url = '/o/oauth2/auth?access_type=offline&include_granted_scopes=true' config.google.auth_url = '/o/oauth2/auth?access_type=offline&include_granted_scopes=true'
# #
# For Microsoft Graph, the key will be your App ID, and the secret will be your app password/public key. # For Microsoft Graph, the key will be your App ID, and the secret will be your app password/public key.
@ -407,7 +409,7 @@ Rails.application.config.sorcery.configure do |config|
# #
# user.magic_login_token_expires_at_attribute_name = # user.magic_login_token_expires_at_attribute_name =
# When was magic login email sent — used for hammering protection. # When was magic login email sent - for hammering protection.
# Default: `:magic_login_email_sent_at` # Default: `:magic_login_email_sent_at`
# #
# user.magic_login_email_sent_at_attribute_name = # user.magic_login_email_sent_at_attribute_name =
@ -528,5 +530,5 @@ Rails.application.config.sorcery.configure do |config|
# This line must come after the 'user config' block. # This line must come after the 'user config' block.
# Define which model authenticates with sorcery. # Define which model authenticates with sorcery.
config.user_class = "User" config.user_class = 'User'
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# This file contains settings for ActionController::ParamsWrapper which # This file contains settings for ActionController::ParamsWrapper which

View File

@ -1,23 +1,25 @@
# frozen_string_literal: true
# Puma can serve each request in a thread from an internal thread pool. # Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum. # The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match # Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum # the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record. # and maximum; this matches the default thread size of Active Record.
# #
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
threads min_threads_count, max_threads_count threads min_threads_count, max_threads_count
# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
# #
port ENV.fetch("PORT") { 3000 } port ENV.fetch('PORT') { 3000 }
# Specifies the `environment` that Puma will run in. # Specifies the `environment` that Puma will run in.
# #
environment ENV.fetch("RAILS_ENV") { "development" } environment ENV.fetch('RAILS_ENV') { 'development' }
# Specifies the `pidfile` that Puma will use. # Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' }
# Specifies the number of `workers` to boot in clustered mode. # Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together # Workers are forked web server processes. If using threads and workers together

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.application.routes.draw do Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
@ -11,23 +13,23 @@ Rails.application.routes.draw do
# short link for submission file uploads # short link for submission file uploads
# we add the filename as part of the URL which allows e.g. Airtable to identify and name the file properly # we add the filename as part of the URL which allows e.g. Airtable to identify and name the file properly
# the constraint makes sure that a . (dot) can be in the filename. e.g. cat.jpg # the constraint makes sure that a . (dot) can be in the filename. e.g. cat.jpg
get '/s/:form_id/:submission_id/:id(/:filename)' => 'file_uploads#show', as: :file_upload, constraints: { filename: /[^\/]+/ } get '/s/:form_id/:submission_id/:id(/:filename)' => 'file_uploads#show', :as => :file_upload, :constraints => { filename: %r{[^/]+} }
# form post url to save new submissions # form post url to save new submissions
post '/s/:form_id' => 'submissions#create', as: :submission post '/s/:form_id' => 'submissions#create', :as => :submission
# short URL for form page # short URL for form page
get '/s/:id/form' => 'forms#form', as: :form_submitter get '/s/:id/form' => 'forms#form', :as => :form_submitter
get 'oauth/callback', to: 'oauths#callback' get 'oauth/callback', to: 'oauths#callback'
get 'oauth/:provider', to: 'oauths#oauth', as: :auth_at_provider get 'oauth/:provider', to: 'oauths#oauth', as: :auth_at_provider
get '/signup' => 'sessions#new', as: :signup # TODO: add proper signup page get '/signup' => 'sessions#new', :as => :signup # TODO: add proper signup page
get '/login' => 'sessions#new', as: :login get '/login' => 'sessions#new', :as => :login
get '/logout' => 'sessions#destroy', as: :logout get '/logout' => 'sessions#destroy', :as => :logout
get '/auth' => 'sessions#auth', as: :auth get '/auth' => 'sessions#auth', :as => :auth
get '/demo(/:backend)' => 'home#demo', as: :demo get '/demo(/:backend)' => 'home#demo', :as => :demo
get '/contact' => 'home#contact', as: :contact get '/contact' => 'home#contact', :as => :contact
get '/help', to: redirect('https://www.notion.so/Tinyforms-Help-Center-04f13b5908bc46cfb4283079a3cb1149') get '/help', to: redirect('https://www.notion.so/Tinyforms-Help-Center-04f13b5908bc46cfb4283079a3cb1149')
get '/form-building-service', to: redirect('https://www.notion.so/Tinyforms-Help-Center-04f13b5908bc46cfb4283079a3cb1149') get '/form-building-service', to: redirect('https://www.notion.so/Tinyforms-Help-Center-04f13b5908bc46cfb4283079a3cb1149')

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
Spring.watch( Spring.watch(
".ruby-version", '.ruby-version',
".rbenv-vars", '.rbenv-vars',
"tmp/restart.txt", 'tmp/restart.txt',
"tmp/caching-dev.txt" 'tmp/caching-dev.txt'
) )

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class CreateAuthentications < ActiveRecord::Migration[6.0] class CreateAuthentications < ActiveRecord::Migration[6.0]
def change def change
create_table :authentications do |t| create_table :authentications do |t|

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class CreateUsers < ActiveRecord::Migration[6.0] class CreateUsers < ActiveRecord::Migration[6.0]
def change def change
create_table :users do |t| create_table :users do |t|

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class CreateForms < ActiveRecord::Migration[6.0] class CreateForms < ActiveRecord::Migration[6.0]
def change def change
create_table :forms do |t| create_table :forms do |t|
@ -7,7 +9,6 @@ class CreateForms < ActiveRecord::Migration[6.0]
t.string :token t.string :token
t.string :thank_you_url t.string :thank_you_url
t.timestamps t.timestamps
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class CreateSubmissions < ActiveRecord::Migration[6.0] class CreateSubmissions < ActiveRecord::Migration[6.0]
def change def change
create_table :submissions do |t| create_table :submissions do |t|

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# This migration comes from active_storage (originally 20170806125915) # This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[5.2] class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
def change def change
@ -10,7 +12,7 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
t.string :checksum, null: false t.string :checksum, null: false
t.datetime :created_at, null: false t.datetime :created_at, null: false
t.index [ :key ], unique: true t.index [:key], unique: true
end end
create_table :active_storage_attachments do |t| create_table :active_storage_attachments do |t|
@ -20,7 +22,7 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
t.datetime :created_at, null: false t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true t.index %i[record_type record_id name blob_id], name: 'index_active_storage_attachments_uniqueness', unique: true
t.foreign_key :active_storage_blobs, column: :blob_id t.foreign_key :active_storage_blobs, column: :blob_id
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddLockboxColumns < ActiveRecord::Migration[6.0] class AddLockboxColumns < ActiveRecord::Migration[6.0]
def change def change
add_column :authentications, :access_token_ciphertext, :text add_column :authentications, :access_token_ciphertext, :text

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddMetadataToSubmissions < ActiveRecord::Migration[6.0] class AddMetadataToSubmissions < ActiveRecord::Migration[6.0]
def change def change
add_column :submissions, :remote_ip, :string add_column :submissions, :remote_ip, :string

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddSequentialIdToSubmissions < ActiveRecord::Migration[6.0] class AddSequentialIdToSubmissions < ActiveRecord::Migration[6.0]
def change def change
add_column :submissions, :sequential_id, :integer add_column :submissions, :sequential_id, :integer

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddTokenToAttachments < ActiveRecord::Migration[6.0] class AddTokenToAttachments < ActiveRecord::Migration[6.0]
def change def change
add_column :active_storage_attachments, :token, :string add_column :active_storage_attachments, :token, :string

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class SorceryCore < ActiveRecord::Migration[6.0] class SorceryCore < ActiveRecord::Migration[6.0]
def change def change
add_column :users, :crypted_password, :string add_column :users, :crypted_password, :string

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddAirtableSupport < ActiveRecord::Migration[6.0] class AddAirtableSupport < ActiveRecord::Migration[6.0]
def change def change
add_column :forms, :airtable_app_key_ciphertext, :string add_column :forms, :airtable_app_key_ciphertext, :string

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# This file is auto-generated from the current state of the database. Instead # This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to # of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition. # incrementally modify your database, and then regenerate this schema definition.
@ -10,84 +12,83 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_04_13_152532) do ActiveRecord::Schema.define(version: 20_200_413_152_532) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension 'plpgsql'
create_table "active_storage_attachments", force: :cascade do |t| create_table 'active_storage_attachments', force: :cascade do |t|
t.string "name", null: false t.string 'name', null: false
t.string "record_type", null: false t.string 'record_type', null: false
t.bigint "record_id", null: false t.bigint 'record_id', null: false
t.bigint "blob_id", null: false t.bigint 'blob_id', null: false
t.datetime "created_at", null: false t.datetime 'created_at', null: false
t.string "token" t.string 'token'
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ['blob_id'], name: 'index_active_storage_attachments_on_blob_id'
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true t.index %w[record_type record_id name blob_id], name: 'index_active_storage_attachments_uniqueness', unique: true
t.index ["token"], name: "index_active_storage_attachments_on_token", unique: true t.index ['token'], name: 'index_active_storage_attachments_on_token', unique: true
end end
create_table "active_storage_blobs", force: :cascade do |t| create_table 'active_storage_blobs', force: :cascade do |t|
t.string "key", null: false t.string 'key', null: false
t.string "filename", null: false t.string 'filename', null: false
t.string "content_type" t.string 'content_type'
t.text "metadata" t.text 'metadata'
t.bigint "byte_size", null: false t.bigint 'byte_size', null: false
t.string "checksum", null: false t.string 'checksum', null: false
t.datetime "created_at", null: false t.datetime 'created_at', null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true t.index ['key'], name: 'index_active_storage_blobs_on_key', unique: true
end end
create_table "authentications", force: :cascade do |t| create_table 'authentications', force: :cascade do |t|
t.integer "user_id" t.integer 'user_id'
t.datetime "expires_at" t.datetime 'expires_at'
t.datetime "created_at", precision: 6, null: false t.datetime 'created_at', precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false t.datetime 'updated_at', precision: 6, null: false
t.text "access_token_ciphertext" t.text 'access_token_ciphertext'
t.text "refresh_token_ciphertext" t.text 'refresh_token_ciphertext'
t.string "provider" t.string 'provider'
t.string "uid" t.string 'uid'
end end
create_table "forms", force: :cascade do |t| create_table 'forms', force: :cascade do |t|
t.integer "user_id" t.integer 'user_id'
t.string "google_spreadsheet_id" t.string 'google_spreadsheet_id'
t.string "title" t.string 'title'
t.string "token" t.string 'token'
t.string "thank_you_url" t.string 'thank_you_url'
t.datetime "created_at", precision: 6, null: false t.datetime 'created_at', precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false t.datetime 'updated_at', precision: 6, null: false
t.string "airtable_app_key_ciphertext" t.string 'airtable_app_key_ciphertext'
t.string "airtable_api_key_ciphertext" t.string 'airtable_api_key_ciphertext'
t.string "airtable_table" t.string 'airtable_table'
t.string "backend_name" t.string 'backend_name'
end end
create_table "submissions", force: :cascade do |t| create_table 'submissions', force: :cascade do |t|
t.integer "form_id" t.integer 'form_id'
t.json "data", default: {} t.json 'data', default: {}
t.datetime "appended_at" t.datetime 'appended_at'
t.datetime "created_at", precision: 6, null: false t.datetime 'created_at', precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false t.datetime 'updated_at', precision: 6, null: false
t.string "remote_ip" t.string 'remote_ip'
t.string "referrer" t.string 'referrer'
t.integer "sequential_id" t.integer 'sequential_id'
end end
create_table "users", force: :cascade do |t| create_table 'users', force: :cascade do |t|
t.string "email" t.string 'email'
t.string "name" t.string 'name'
t.string "google_id" t.string 'google_id'
t.datetime "created_at", precision: 6, null: false t.datetime 'created_at', precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false t.datetime 'updated_at', precision: 6, null: false
t.string "crypted_password" t.string 'crypted_password'
t.string "salt" t.string 'salt'
t.string "magic_login_token" t.string 'magic_login_token'
t.datetime "magic_login_token_expires_at" t.datetime 'magic_login_token_expires_at'
t.datetime "magic_login_email_sent_at" t.datetime 'magic_login_email_sent_at'
t.index ["email"], name: "index_users_on_email", unique: true t.index ['email'], name: 'index_users_on_email', unique: true
t.index ["magic_login_token"], name: "index_users_on_magic_login_token" t.index ['magic_login_token'], name: 'index_users_on_magic_login_token'
end end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key 'active_storage_attachments', 'active_storage_blobs', column: 'blob_id'
end end

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
# This file should contain all the record creation needed to seed the database with its default values. # This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
# #

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true
require 'airrecord' require 'airrecord'
module SpreadsheetBackends module SpreadsheetBackends
class Airtable class Airtable
attr_accessor :form, :user attr_accessor :form, :user
def initialize(form) def initialize(form)
@ -16,8 +17,8 @@ module SpreadsheetBackends
def append(data) def append(data)
result = table.create(data, typecast: true) result = table.create(data, typecast: true)
result.id.present? result.id.present?
rescue Airrecord::Error => e rescue Airrecord::Error
return false false
end end
def create def create

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
require 'google/apis/sheets_v4' require 'google/apis/sheets_v4'
module SpreadsheetBackends module SpreadsheetBackends
class GoogleSheets class GoogleSheets
# Hash to translate an index to a A1 notation. e.g. 1 => 'B', 27 => 'AA' # Hash to translate an index to a A1 notation. e.g. 1 => 'B', 27 => 'AA'
COLUMN_INDEX_TO_LETTER = Hash.new {|hash,key| hash[key] = hash[key - 1].next }.merge({0 => "A"}) COLUMN_INDEX_TO_LETTER = Hash.new { |hash, key| hash[key] = hash[key - 1].next }.merge({ 0 => 'A' })
attr_accessor :form, :user attr_accessor :form, :user
@ -30,17 +32,17 @@ module SpreadsheetBackends
def create def create
sheets = Google::Apis::SheetsV4::SheetsService.new sheets = Google::Apis::SheetsV4::SheetsService.new
sheets.authorization = user.google_authorization sheets.authorization = user.google_authorization
create_object = Google::Apis::SheetsV4::Spreadsheet.new(properties: { title: form.title}) create_object = Google::Apis::SheetsV4::Spreadsheet.new(properties: { title: form.title })
spreadsheet = sheets.create_spreadsheet(create_object) spreadsheet = sheets.create_spreadsheet(create_object)
form.update(google_spreadsheet_id: spreadsheet.spreadsheet_id) form.update(google_spreadsheet_id: spreadsheet.spreadsheet_id)
end end
def headers def headers
@headers ||= begin @headers ||= begin
values = spreadsheet_service.get_spreadsheet_values(form.google_spreadsheet_id, 'A1:An').values values = spreadsheet_service.get_spreadsheet_values(form.google_spreadsheet_id, 'A1:An').values
# if there are no headers yet, return an empty array # if there are no headers yet, return an empty array
if values if values
values[0].map(&:strip) values[0].map(&:strip)
else else
[] []
end end
@ -66,6 +68,5 @@ module SpreadsheetBackends
spreadsheet_service.update_spreadsheet_value(form.google_spreadsheet_id, range, value_range, value_input_option: 'USER_ENTERED') spreadsheet_service.update_spreadsheet_value(form.google_spreadsheet_id, range, value_range, value_input_option: 'USER_ENTERED')
@headers = nil # reset header values to refresh memoization on next access @headers = nil # reset header values to refresh memoization on next access
end end
end end
end end