Merge branch 'master' into setup/pagination
This commit is contained in:
80 files changed
+564
-236
No files matched your search
@@ -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
|
||||
@@ -1,15 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||
|
||||
ruby '2.6.1'
|
||||
|
||||
gem 'rails'
|
||||
gem 'jbuilder'
|
||||
gem 'pg'
|
||||
gem 'puma'
|
||||
gem 'rails'
|
||||
gem 'sass-rails'
|
||||
gem 'webpacker'
|
||||
gem 'turbolinks'
|
||||
gem 'jbuilder'
|
||||
gem 'webpacker'
|
||||
# Use Redis adapter to run Action Cable in production
|
||||
# gem 'redis', '~> 4.0'
|
||||
# Use Active Model has_secure_password
|
||||
@@ -23,8 +25,8 @@ gem 'bootsnap', '>= 1.4.2', require: false
|
||||
|
||||
gem 'lockbox'
|
||||
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'airrecord'
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'google-api-client'
|
||||
gem 'rack-cors'
|
||||
gem 'sentry-raven'
|
||||
@@ -34,8 +36,11 @@ gem 'pagy'
|
||||
|
||||
group :development, :test do
|
||||
# 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 'rubocop', require: false
|
||||
gem 'rubocop-rails', require: false
|
||||
gem 'rubocop-rspec', require: false
|
||||
end
|
||||
|
||||
group :development do
|
||||
@@ -57,4 +62,4 @@ group :test do
|
||||
end
|
||||
|
||||
# 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]
|
||||
@@ -61,6 +61,7 @@ GEM
|
||||
airrecord (1.0.5)
|
||||
faraday (>= 0.10, < 2.0)
|
||||
net-http-persistent (>= 2.9)
|
||||
ast (2.4.0)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.263.0)
|
||||
aws-sdk-core (3.89.1)
|
||||
@@ -115,6 +116,7 @@ GEM
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jaro_winkler (1.5.4)
|
||||
jbuilder (2.10.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.4.0)
|
||||
@@ -154,6 +156,9 @@ GEM
|
||||
rack (>= 1.2, < 3)
|
||||
os (1.1.0)
|
||||
pagy (3.8.0)
|
||||
parallel (1.19.1)
|
||||
parser (2.7.1.1)
|
||||
ast (~> 2.4.0)
|
||||
pg (1.2.3)
|
||||
public_suffix (4.0.4)
|
||||
puma (4.3.3)
|
||||
@@ -191,6 +196,7 @@ GEM
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.1)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.1)
|
||||
@@ -200,6 +206,22 @@ GEM
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
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)
|
||||
sassc-rails (~> 2.1, >= 2.1.1)
|
||||
sassc (2.2.1)
|
||||
@@ -246,6 +268,7 @@ GEM
|
||||
tzinfo (1.2.7)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (1.7.0)
|
||||
webpacker (5.0.1)
|
||||
activesupport (>= 5.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
@@ -274,6 +297,9 @@ DEPENDENCIES
|
||||
puma
|
||||
rack-cors
|
||||
rails
|
||||
rubocop
|
||||
rubocop-rails
|
||||
rubocop-rspec
|
||||
sass-rails
|
||||
sentry-raven
|
||||
sequenced
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ $weight-bold: 400;
|
||||
|
||||
@import "checkmark-icon";
|
||||
@import 'demo';
|
||||
@import 'highlight';
|
||||
@import 'prism';
|
||||
|
||||
.field_with_errors input {
|
||||
border-color: $red;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
code.html .hljs-tag:first-child .hljs-attr, code.html .hljs-tag:first-child .hljs-string {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/* PrismJS 1.20.0
|
||||
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript&plugins=custom-class */
|
||||
/**
|
||||
* okaidia theme for JavaScript, CSS and HTML
|
||||
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
|
||||
* @author ocodia
|
||||
*/
|
||||
|
||||
code.language-html > .prism-tag:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #f8f8f2;
|
||||
background: none;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #272822;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.prism-token.prism-comment,
|
||||
.prism-token.prism-prolog,
|
||||
.prism-token.prism-doctype,
|
||||
.prism-token.prism-cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.prism-token.prism-punctuation {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.prism-token.prism-namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.prism-token.prism-property,
|
||||
.prism-token.prism-tag,
|
||||
.prism-token.prism-constant,
|
||||
.prism-token.prism-symbol,
|
||||
.prism-token.prism-deleted {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.prism-token.prism-boolean,
|
||||
.prism-token.prism-number {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.prism-token.prism-selector,
|
||||
.prism-token.prism-attr-name,
|
||||
.prism-token.prism-string,
|
||||
.prism-token.prism-char,
|
||||
.prism-token.prism-builtin,
|
||||
.prism-token.prism-inserted {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.prism-token.prism-operator,
|
||||
.prism-token.prism-entity,
|
||||
.prism-token.prism-url,
|
||||
.prism-language-css .prism-token.prism-string,
|
||||
.prism-style .prism-token.prism-string,
|
||||
.prism-token.prism-variable {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.prism-token.prism-atrule,
|
||||
.prism-token.prism-attr-value,
|
||||
.prism-token.prism-function,
|
||||
.prism-token.prism-class-name {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.prism-token.prism-keyword {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.prism-token.prism-regex,
|
||||
.token.important {
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.prism-token.prism-important,
|
||||
.prism-token.prism-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.prism-token.prism-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.prism-token.prism-entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
end
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
helper_method :current_user, :logged_in?
|
||||
include Pagy::Backend
|
||||
|
||||
def require_login
|
||||
redirect_to login_url unless current_user.present?
|
||||
redirect_to login_url if current_user.blank?
|
||||
end
|
||||
|
||||
def current_user
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class FileUploadsController < ApplicationController
|
||||
def show
|
||||
@form = Form.find_by!(token: params[:form_id])
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'google/apis/sheets_v4'
|
||||
require 'google/api_client/client_secrets'
|
||||
class FormsController < ApplicationController
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class HomeController < ApplicationController
|
||||
# frozen_string_literal: true
|
||||
|
||||
class HomeController < ApplicationController
|
||||
def index
|
||||
redirect_to forms_url if logged_in?
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class OauthsController < ApplicationController
|
||||
# frozen_string_literal: true
|
||||
|
||||
class OauthsController < ApplicationController
|
||||
# Sends the user on a trip to the provider,
|
||||
# and after authorizing there back to the callback url.
|
||||
def oauth
|
||||
@@ -9,26 +10,25 @@ class OauthsController < ApplicationController
|
||||
def callback
|
||||
provider = params[: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
|
||||
begin
|
||||
@user = create_from(provider)
|
||||
if authentication = @user.authentications.find_by(provider: provider)
|
||||
authentication.update({
|
||||
access_token: @access_token.token,
|
||||
refresh_token: @access_token.refresh_token,
|
||||
expires_at: Time.at(@access_token.expires_at)
|
||||
})
|
||||
access_token: @access_token.token,
|
||||
refresh_token: @access_token.refresh_token,
|
||||
expires_at: Time.zone.at(@access_token.expires_at)
|
||||
})
|
||||
end
|
||||
|
||||
reset_session
|
||||
auto_login(@user)
|
||||
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
|
||||
rescue
|
||||
redirect_to root_path, notice: "Logged in from #{provider.titleize}!"
|
||||
rescue StandardError
|
||||
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
|
||||
@@ -1,5 +1,6 @@
|
||||
class SessionsController < ApplicationController
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SessionsController < ApplicationController
|
||||
def new
|
||||
reset_session
|
||||
end
|
||||
@@ -8,5 +9,4 @@ class SessionsController < ApplicationController
|
||||
reset_session
|
||||
redirect_to root_url
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'google/apis/sheets_v4'
|
||||
class SubmissionsController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
@@ -14,10 +16,14 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
respond_to do |format|
|
||||
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 }) }
|
||||
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) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationHelper
|
||||
include Pagy::Frontend
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
document.addEventListener("turbolinks:load", function () {
|
||||
document.querySelectorAll('pre code').forEach(function (block) {
|
||||
if (window.hljs) {
|
||||
hljs.highlightBlock(block);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ require('tinyforms');
|
||||
require('demo');
|
||||
require('backend_typed');
|
||||
require('tabs');
|
||||
require('highlight');
|
||||
require('prism');
|
||||
|
||||
// Uncomment to copy all static images under ../images to the output folder and reference
|
||||
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
window.Prism = window.Prism || {};
|
||||
window.Prism.manual = true;
|
||||
document.addEventListener("turbolinks:load", function () {
|
||||
Prism.plugins.customClass.prefix('prism-');
|
||||
document.querySelectorAll('pre code').forEach(function (el) {
|
||||
Prism.highlightElement(el);
|
||||
});
|
||||
});
|
||||
|
||||
/* PrismJS 1.20.0
|
||||
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript&plugins=custom-class */
|
||||
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,C={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof _?new _(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,r){var a,n,l=C.util.type(e);switch(r=r||{},l){case"Object":if(n=C.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case"Array":return n=C.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e&&!c.test(e.className);)e=e.parentElement;return e?(e.className.match(c)||[,"none"])[1].toLowerCase():"none"},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName("script");for(var r in t)if(t[r].src==n)return t[r]}return null}}},languages:{extend:function(e,n){var t=C.util.clone(C.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||C.languages)[t],l={};for(var i in a)if(a.hasOwnProperty(i)){if(i==e)for(var o in n)n.hasOwnProperty(o)&&(l[o]=n[o]);n.hasOwnProperty(i)||(l[i]=a[i])}var s=r[t];return r[t]=l,C.languages.DFS(C.languages,function(e,n){n===s&&e!=t&&(this[e]=l)}),l},DFS:function e(n,t,r,a){a=a||{};var l=C.util.objId;for(var i in n)if(n.hasOwnProperty(i)){t.call(n,i,n[i],r||i);var o=n[i],s=C.util.type(o);"Object"!==s||a[l(o)]?"Array"!==s||a[l(o)]||(a[l(o)]=!0,e(o,t,i,a)):(a[l(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){C.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};C.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),C.hooks.run("before-all-elements-highlight",r);for(var a,l=0;a=r.elements[l++];)C.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=C.util.getLanguage(e),a=C.languages[r];e.className=e.className.replace(c,"").replace(/\s+/g," ")+" language-"+r;var l=e.parentNode;l&&"pre"===l.nodeName.toLowerCase()&&(l.className=l.className.replace(c,"").replace(/\s+/g," ")+" language-"+r);var i={element:e,language:r,grammar:a,code:e.textContent};function o(e){i.highlightedCode=e,C.hooks.run("before-insert",i),i.element.innerHTML=i.highlightedCode,C.hooks.run("after-highlight",i),C.hooks.run("complete",i),t&&t.call(i.element)}if(C.hooks.run("before-sanity-check",i),!i.code)return C.hooks.run("complete",i),void(t&&t.call(i.element));if(C.hooks.run("before-highlight",i),i.grammar)if(n&&u.Worker){var s=new Worker(C.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:i.language,code:i.code,immediateClose:!0}))}else o(C.highlight(i.code,i.grammar,i.language));else o(C.util.encode(i.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};return C.hooks.run("before-tokenize",r),r.tokens=C.tokenize(r.code,r.grammar),C.hooks.run("after-tokenize",r),_.stringify(C.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new l;return M(a,a.head,e),function e(n,t,r,a,l,i,o){for(var s in r)if(r.hasOwnProperty(s)&&r[s]){var u=r[s];u=Array.isArray(u)?u:[u];for(var c=0;c<u.length;++c){if(o&&o==s+","+c)return;var g=u[c],f=g.inside,h=!!g.lookbehind,d=!!g.greedy,v=0,p=g.alias;if(d&&!g.pattern.global){var m=g.pattern.toString().match(/[imsuy]*$/)[0];g.pattern=RegExp(g.pattern.source,m+"g")}g=g.pattern||g;for(var y=a.next,k=l;y!==t.tail;k+=y.value.length,y=y.next){var b=y.value;if(t.length>n.length)return;if(!(b instanceof _)){var x=1;if(d&&y!=t.tail.prev){g.lastIndex=k;var w=g.exec(n);if(!w)break;var A=w.index+(h&&w[1]?w[1].length:0),P=w.index+w[0].length,S=k;for(S+=y.value.length;S<=A;)y=y.next,S+=y.value.length;if(S-=y.value.length,k=S,y.value instanceof _)continue;for(var O=y;O!==t.tail&&(S<P||"string"==typeof O.value&&!O.prev.value.greedy);O=O.next)x++,S+=O.value.length;x--,b=n.slice(k,S),w.index-=k}else{g.lastIndex=0;var w=g.exec(b)}if(w){h&&(v=w[1]?w[1].length:0);var A=w.index+v,w=w[0].slice(v),P=A+w.length,E=b.slice(0,A),N=b.slice(P),j=y.prev;E&&(j=M(t,j,E),k+=E.length),W(t,j,x);var L=new _(s,f?C.tokenize(w,f):w,p,w,d);if(y=M(t,j,L),N&&M(t,y,N),1<x&&e(n,t,r,y.prev,k,!0,s+","+c),i)break}else if(i)break}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.nLine truncated
|
||||
Prism.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0},cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var n={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[^]*?>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
|
||||
!function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"}}},url:{pattern:RegExp("url\\((?:"+e.source+"|[^\n\r()]*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+e.source+")*?(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),s.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:t.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:s.languages.css}},alias:"language-css"}},t.tag))}(Prism);
|
||||
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
|
||||
Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&|\|\||[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?[.?]?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript;
|
||||
!function(){if("undefined"!=typeof self&&self.Prism||"undefined"!=typeof global&&global.Prism){var a,e,t="";Prism.plugins.customClass={add:function(n){a=n},map:function(s){e="function"==typeof s?s:function(n){return s[n]||n}},prefix:function(n){t=n||""}},Prism.hooks.add("wrap",function(s){if(a){var n=a({content:s.content,type:s.type,language:s.language});Array.isArray(n)?s.classes.push.apply(s.classes,n):n&&s.classes.push(n)}(e||t)&&(s.classes=s.classes.map(function(n){return t+(e?e(n,s.language):n)}))})}}();
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationJob < ActiveJob::Base
|
||||
# Automatically retry jobs that encountered a deadlock
|
||||
# retry_on ActiveRecord::Deadlocked
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionAppendJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |exception|
|
||||
submission_id = self.arguments.first
|
||||
rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |_exception|
|
||||
submission_id = arguments.first
|
||||
Rails.logger.error("AuthorizationError during SubmissionAppend: submission_id=#{submission_id}")
|
||||
submission = Submission.find(submission_id)
|
||||
submission.form.deactivate!('AuthorizationError')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'from@example.com'
|
||||
layout 'mailer'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
||||
@@ -1,7 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Authentication < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
scope :for, -> (provider) { where(provider: provider) }
|
||||
scope :for, ->(provider) { where(provider: provider) }
|
||||
|
||||
encrypts :access_token
|
||||
encrypts :refresh_token
|
||||
@@ -10,19 +12,24 @@ class Authentication < ApplicationRecord
|
||||
expires_at <= Time.current
|
||||
end
|
||||
|
||||
def refresh_from(client_secret)
|
||||
client_secret.refresh!
|
||||
self.access_token = client_secret.access_token if client_secret.access_token.present?
|
||||
self.refresh_token = client_secret.refresh_token if client_secret.refresh_token.present?
|
||||
self.expires_at = Time.zone.at(client_secret.expires_at) if client_secret.expires_at.present?
|
||||
save
|
||||
end
|
||||
|
||||
def google_authorization
|
||||
return nil unless provider == 'google'
|
||||
|
||||
@google_authorization ||= CLIENT_SECRETS.to_authorization.tap do |c|
|
||||
c.access_token = self.access_token
|
||||
c.refresh_token = self.refresh_token
|
||||
c.expires_at = self.expires_at
|
||||
c.access_token = access_token
|
||||
c.refresh_token = refresh_token
|
||||
c.expires_at = expires_at
|
||||
if expires_at < 1.minute.from_now
|
||||
c.refresh!
|
||||
self.access_token = c.access_token if c.access_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?
|
||||
refresh_from(c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
+9
-9
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Form < ApplicationRecord
|
||||
belongs_to :user
|
||||
has_many :submissions, dependent: :destroy
|
||||
@@ -10,12 +12,12 @@ class Form < ApplicationRecord
|
||||
encrypts :airtable_api_key
|
||||
encrypts :airtable_app_key
|
||||
|
||||
validates_presence_of :title
|
||||
validates_inclusion_of :backend_name, in: ['google_sheets', 'airtable']
|
||||
validates :title, presence: true
|
||||
validates :backend_name, inclusion: { in: %w[google_sheets airtable] }
|
||||
# Airtable validations
|
||||
validates_presence_of :airtable_api_key, if: :airtable?
|
||||
validates_presence_of :airtable_app_key, if: :airtable?
|
||||
validates_presence_of :airtable_table, if: :airtable?
|
||||
validates :airtable_api_key, presence: { if: :airtable? }
|
||||
validates :airtable_app_key, presence: { if: :airtable? }
|
||||
validates :airtable_table, presence: { if: :airtable? }
|
||||
|
||||
# TODO: use counter_cache option on association
|
||||
def submissions_count
|
||||
@@ -27,12 +29,10 @@ class Form < ApplicationRecord
|
||||
end
|
||||
|
||||
def deactivate!(reason = nil)
|
||||
self.user.deactivate!(reason)
|
||||
user.deactivate!(reason)
|
||||
end
|
||||
|
||||
def active?
|
||||
self.user.active?
|
||||
end
|
||||
delegate :active?, to: :user
|
||||
|
||||
def airtable?
|
||||
backend_name == 'airtable'
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Submission < ApplicationRecord
|
||||
belongs_to :form
|
||||
has_many_attached :files
|
||||
|
||||
acts_as_sequenced scope: :form_id
|
||||
|
||||
validates_presence_of :data, if: :appended_at?
|
||||
validates :data, presence: { if: :appended_at? }
|
||||
|
||||
def process_data(submitted_data)
|
||||
processed_data = {}
|
||||
@@ -12,7 +14,7 @@ class Submission < ApplicationRecord
|
||||
processed_data[key] = submission_value_for(value)
|
||||
end
|
||||
update_attribute(:data, processed_data)
|
||||
SubmissionAppendJob.perform_later(self.id)
|
||||
SubmissionAppendJob.perform_later(id)
|
||||
end
|
||||
|
||||
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.save
|
||||
# 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
|
||||
value.to_s
|
||||
end
|
||||
|
||||
+3
-1
@@ -1,9 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class User < ApplicationRecord
|
||||
authenticates_with_sorcery!
|
||||
has_many :authentications, 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
|
||||
authentications.last&.update(expires_at: Time.current)
|
||||
end
|
||||
|
||||
@@ -6,6 +6,16 @@
|
||||
<%= text_field_tag header %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% if form.spreadsheet_headers.blank? %>
|
||||
<p>
|
||||
<label for="Name">Name</label>
|
||||
<input type="text" name="Name" required>
|
||||
</p>
|
||||
<p>
|
||||
<label for="Email">Email</label>
|
||||
<input type="email" name="Email" required>
|
||||
</p>
|
||||
<% end %>
|
||||
...
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
@@ -7,5 +7,4 @@
|
||||
</p>
|
||||
<% end %>
|
||||
...
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
@@ -42,7 +42,7 @@
|
||||
<p>
|
||||
Please set your form action as in the example below. Make sure to use proper <code>name</code> attributes.
|
||||
<p>
|
||||
<pre><code class="html">
|
||||
<pre><code class="language-html">
|
||||
<%= html_escape_once render('form_example', form: @form) %>
|
||||
</code></pre>
|
||||
|
||||
@@ -50,12 +50,9 @@
|
||||
<p>
|
||||
Learn more about our <a href="https://www.notion.so/JavaScript-Library-442071548edd4cd1af9c7d8edb3d42cb">JavaScript library</a> and our <a href="https://www.notion.so/Tinyforms-API-7862355e22ce4bbead5de3b635cda55e">API</a> in your help section.
|
||||
</p>
|
||||
<pre><code class="html">
|
||||
<pre><code class="language-html">
|
||||
<%= html_escape_once render('form_example_js', form: @form) %>
|
||||
</code></pre>
|
||||
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/styles/railscasts.min.css">
|
||||
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/highlight.min.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,22 +24,23 @@
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-two-thirds">
|
||||
|
||||
<h3 class="title">Setup your online form:</h3>
|
||||
<h3 class="title">One more step:</h3>
|
||||
<p>
|
||||
Your form submission URL: <code><%= submission_url(@form) %></code>
|
||||
</p>
|
||||
<p>
|
||||
Set the <code>action</code> of your online form to this URL and make sure your fields have proper <code>name</code> attributes.
|
||||
Set the <code>action</code> of your online form to <code><%= submission_url(@form) %></code> and make sure your fields have proper <code>name</code> attributes.
|
||||
</p>
|
||||
<h4 class="subtitle" style="margin-top:1em">Example:</h4>
|
||||
<pre><code class="html">
|
||||
<pre><code class="language-html">
|
||||
<%= html_escape_once render('form_example', form: @form) %>
|
||||
</code></pre>
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/styles/default.min.css">
|
||||
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/highlight.min.js"></script>
|
||||
|
||||
<p>
|
||||
Let us know if you need <%= link_to 'help', help_url %> - we also build the form for you!
|
||||
If you prefer to submit the form using JavaScript/AJAX have a look at our <a href="https://www.notion.so/JavaScript-Library-442071548edd4cd1af9c7d8edb3d42cb">JavaScript library</a> and our <a href="https://www.notion.so/Tinyforms-API-7862355e22ce4bbead5de3b635cda55e">API</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you need help, <%= link_to "we're here!", help_url %>
|
||||
</p>
|
||||
<p style="margin-top:15px">
|
||||
And if you do not want to deal with your form at all, have a look at our <%= link_to 'form building service', form_building_service_url %>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<% end %>
|
||||
<hr>
|
||||
<p>
|
||||
Simply login with your existing Google account.
|
||||
Login with your existing Google account, no new credentials needed.
|
||||
<br>
|
||||
Do you need help? <%= link_to 'let us know', help_url %>.
|
||||
</p>
|
||||
|
||||
+24
-20
@@ -8,46 +8,48 @@
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "rubygems"
|
||||
require 'rubygems'
|
||||
|
||||
m = Module.new do
|
||||
module_function
|
||||
|
||||
def invoked_as_script?
|
||||
File.expand_path($0) == File.expand_path(__FILE__)
|
||||
File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__)
|
||||
end
|
||||
|
||||
def env_var_version
|
||||
ENV["BUNDLER_VERSION"]
|
||||
ENV['BUNDLER_VERSION']
|
||||
end
|
||||
|
||||
def cli_arg_version
|
||||
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
|
||||
update_index = nil
|
||||
ARGV.each_with_index do |a, i|
|
||||
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
||||
bundler_version = a
|
||||
end
|
||||
bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
||||
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
||||
bundler_version = $1
|
||||
|
||||
bundler_version = Regexp.last_match(1)
|
||||
update_index = i
|
||||
end
|
||||
bundler_version
|
||||
end
|
||||
|
||||
def gemfile
|
||||
gemfile = ENV["BUNDLE_GEMFILE"]
|
||||
gemfile = ENV['BUNDLE_GEMFILE']
|
||||
return gemfile if gemfile && !gemfile.empty?
|
||||
|
||||
File.expand_path("../../Gemfile", __FILE__)
|
||||
File.expand_path('../Gemfile', __dir__)
|
||||
end
|
||||
|
||||
def lockfile
|
||||
lockfile =
|
||||
case File.basename(gemfile)
|
||||
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
|
||||
when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile)
|
||||
else "#{gemfile}.lock"
|
||||
end
|
||||
File.expand_path(lockfile)
|
||||
@@ -55,15 +57,17 @@ m = Module.new do
|
||||
|
||||
def lockfile_version
|
||||
return unless File.file?(lockfile)
|
||||
|
||||
lockfile_contents = File.read(lockfile)
|
||||
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
||||
|
||||
Regexp.last_match(1)
|
||||
end
|
||||
|
||||
def bundler_version
|
||||
@bundler_version ||=
|
||||
env_var_version || cli_arg_version ||
|
||||
lockfile_version
|
||||
lockfile_version
|
||||
end
|
||||
|
||||
def bundler_requirement
|
||||
@@ -73,28 +77,30 @@ m = Module.new do
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
def load_bundler!
|
||||
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
||||
ENV['BUNDLE_GEMFILE'] ||= gemfile
|
||||
|
||||
activate_bundler
|
||||
end
|
||||
|
||||
def activate_bundler
|
||||
gem_error = activation_error_handling do
|
||||
gem "bundler", bundler_requirement
|
||||
gem 'bundler', bundler_requirement
|
||||
end
|
||||
return if gem_error.nil?
|
||||
|
||||
require_error = activation_error_handling do
|
||||
require "bundler/version"
|
||||
require 'bundler/version'
|
||||
end
|
||||
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}'`"
|
||||
exit 42
|
||||
end
|
||||
@@ -109,6 +115,4 @@ end
|
||||
|
||||
m.load_bundler!
|
||||
|
||||
if m.invoked_as_script?
|
||||
load Gem.bin_path("bundler", "bundle")
|
||||
end
|
||||
load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script?
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
load File.expand_path('../spring', __FILE__)
|
||||
load File.expand_path('spring', __dir__)
|
||||
rescue LoadError => e
|
||||
raise unless e.message.include?('spring')
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
load File.expand_path('../spring', __FILE__)
|
||||
load File.expand_path('spring', __dir__)
|
||||
rescue LoadError => e
|
||||
raise unless e.message.include?('spring')
|
||||
end
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
# path to your application root.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This file loads Spring without using Bundler, in order to be fast.
|
||||
# It gets overwritten when you run the `spring binstub` command.
|
||||
|
||||
+10
-9
@@ -1,18 +1,19 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
||||
ENV["NODE_ENV"] ||= "development"
|
||||
ENV['RAILS_ENV'] ||= ENV['RACK_ENV'] || 'development'
|
||||
ENV['NODE_ENV'] ||= 'development'
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
require 'pathname'
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
require "bundler/setup"
|
||||
require 'bundler/setup'
|
||||
|
||||
require "webpacker"
|
||||
require "webpacker/webpack_runner"
|
||||
require 'webpacker'
|
||||
require 'webpacker/webpack_runner'
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
Webpacker::WebpackRunner.run(ARGV)
|
||||
end
|
||||
+10
-9
@@ -1,18 +1,19 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
||||
ENV["NODE_ENV"] ||= "development"
|
||||
ENV['RAILS_ENV'] ||= ENV['RACK_ENV'] || 'development'
|
||||
ENV['NODE_ENV'] ||= 'development'
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
require 'pathname'
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
require "bundler/setup"
|
||||
require 'bundler/setup'
|
||||
|
||||
require "webpacker"
|
||||
require "webpacker/dev_server_runner"
|
||||
require 'webpacker'
|
||||
require 'webpacker/dev_server_runner'
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
Webpacker::DevServerRunner.run(ARGV)
|
||||
end
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
begin
|
||||
exec "yarnpkg", *ARGV
|
||||
rescue Errno::ENOENT
|
||||
$stderr.puts "Yarn executable was not detected in the system."
|
||||
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
|
||||
exit 1
|
||||
end
|
||||
exec 'yarnpkg', *ARGV
|
||||
rescue Errno::ENOENT
|
||||
warn 'Yarn executable was not detected in the system.'
|
||||
warn 'Download Yarn at https://yarnpkg.com/en/docs/install'
|
||||
exit 1
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This file is used by Rack-based servers to start the application.
|
||||
|
||||
require_relative 'config/environment'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'boot'
|
||||
|
||||
require 'rails/all'
|
||||
@@ -29,7 +31,7 @@ module Tinyform
|
||||
config.middleware.insert_before 0, Rack::Cors do
|
||||
allow do
|
||||
origins '*'
|
||||
resource '/s/*', headers: :any, methods: [:post, :put, :options]
|
||||
resource '/s/*', headers: :any, methods: %i[post put options]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
||||
|
||||
require 'bundler/setup' # Set up gems listed in the Gemfile.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Load the Rails application.
|
||||
require_relative 'application'
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Rails.application.configure do
|
||||
# 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.
|
||||
# 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.enable_fragment_cache_logging = true
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
||||
@@ -51,7 +53,7 @@ Rails.application.configure do
|
||||
config.log_level = :info
|
||||
|
||||
# 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.
|
||||
# config.cache_store = :mem_cache_store
|
||||
@@ -80,7 +82,7 @@ Rails.application.configure do
|
||||
# require 'syslog/logger'
|
||||
# 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.formatter = config.log_formatter
|
||||
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# The test environment is used exclusively to run your application's
|
||||
# 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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# ActiveSupport::Reloader.to_prepare do
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Rails.configuration.to_prepare do
|
||||
ActiveStorage::Attachment.send(:has_secure_token)
|
||||
end
|
||||
@@ -1,3 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
# 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.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Define an application-wide content security policy
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Specify a serializer for the signed and encrypted cookie jars.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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_EMBED_URL = ENV['AIRTABLE_DEMO_EMBED_URL']
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Configure sensitive parameters which will be filtered from the log file.
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'google/api_client/client_secrets'
|
||||
require 'google/apis'
|
||||
|
||||
secrets_options = {
|
||||
"client_id" => ENV['GOOGLE_CLIENT_ID'],
|
||||
"project_id" => ENV['GOOGLE_PROJECT_ID'],
|
||||
"client_secret" => ENV['GOOGLE_CLIENT_SECRET'],
|
||||
"auth_uri" => "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri" => "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url" => "https://www.googleapis.com/oauth2/v1/certs"
|
||||
'client_id' => ENV['GOOGLE_CLIENT_ID'],
|
||||
'project_id' => ENV['GOOGLE_PROJECT_ID'],
|
||||
'client_secret' => ENV['GOOGLE_CLIENT_SECRET'],
|
||||
'auth_uri' => 'https://accounts.google.com/o/oauth2/auth',
|
||||
'token_uri' => 'https://oauth2.googleapis.com/token',
|
||||
'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
|
||||
@@ -1 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
DEFAULT_HOST = ENV['DEFAULT_HOST'] || 'localhost:3000'
|
||||
@@ -1,3 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Add new inflection rules using the following format. Inflections
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Add new mime types for use in respond_to blocks:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# 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).
|
||||
#
|
||||
# Available submodules are: :user_activation, :http_basic_auth, :remember_me,
|
||||
# :reset_password, :session_timeout, :brute_force_protection, :activity_logging,
|
||||
# :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.
|
||||
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.secret = ENV['GOOGLE_CLIENT_SECRET']
|
||||
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.scope = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/spreadsheets"
|
||||
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.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'
|
||||
#
|
||||
# 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 =
|
||||
|
||||
# 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`
|
||||
#
|
||||
# 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.
|
||||
# Define which model authenticates with sorcery.
|
||||
config.user_class = "User"
|
||||
config.user_class = 'User'
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# This file contains settings for ActionController::ParamsWrapper which
|
||||
|
||||
+7
-5
@@ -1,23 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Puma can serve each request in a thread from an internal thread pool.
|
||||
# The `threads` method setting takes two numbers: a minimum and maximum.
|
||||
# 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
|
||||
# and maximum; this matches the default thread size of Active Record.
|
||||
#
|
||||
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
|
||||
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
|
||||
max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }
|
||||
min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
|
||||
threads min_threads_count, max_threads_count
|
||||
|
||||
# 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.
|
||||
#
|
||||
environment ENV.fetch("RAILS_ENV") { "development" }
|
||||
environment ENV.fetch('RAILS_ENV') { 'development' }
|
||||
|
||||
# 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.
|
||||
# Workers are forked web server processes. If using threads and workers together
|
||||
|
||||
+12
-10
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Rails.application.routes.draw do
|
||||
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
|
||||
|
||||
@@ -11,25 +13,25 @@ Rails.application.routes.draw do
|
||||
# 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
|
||||
# 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
|
||||
post '/s/:form_id' => 'submissions#create', as: :submission
|
||||
post '/s/:form_id' => 'submissions#create', :as => :submission
|
||||
# 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/:provider', to: 'oauths#oauth', as: :auth_at_provider
|
||||
|
||||
get '/signup' => 'sessions#new', as: :signup # TODO: add proper signup page
|
||||
get '/login' => 'sessions#new', as: :login
|
||||
get '/logout' => 'sessions#destroy', as: :logout
|
||||
get '/auth' => 'sessions#auth', as: :auth
|
||||
get '/signup' => 'sessions#new', :as => :signup # TODO: add proper signup page
|
||||
get '/login' => 'sessions#new', :as => :login
|
||||
get '/logout' => 'sessions#destroy', :as => :logout
|
||||
get '/auth' => 'sessions#auth', :as => :auth
|
||||
|
||||
get '/demo(/:backend)' => 'home#demo', as: :demo
|
||||
get '/contact' => 'home#contact', as: :contact
|
||||
get '/demo(/:backend)' => 'home#demo', :as => :demo
|
||||
get '/contact' => 'home#contact', :as => :contact
|
||||
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')
|
||||
|
||||
root 'home#index'
|
||||
end
|
||||
+6
-4
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Spring.watch(
|
||||
".ruby-version",
|
||||
".rbenv-vars",
|
||||
"tmp/restart.txt",
|
||||
"tmp/caching-dev.txt"
|
||||
'.ruby-version',
|
||||
'.rbenv-vars',
|
||||
'tmp/restart.txt',
|
||||
'tmp/caching-dev.txt'
|
||||
)
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateAuthentications < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :authentications do |t|
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateUsers < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :users do |t|
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateForms < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :forms do |t|
|
||||
@@ -7,7 +9,6 @@ class CreateForms < ActiveRecord::Migration[6.0]
|
||||
t.string :token
|
||||
t.string :thank_you_url
|
||||
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateSubmissions < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :submissions do |t|
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This migration comes from active_storage (originally 20170806125915)
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
@@ -10,7 +12,7 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||
t.string :checksum, null: false
|
||||
t.datetime :created_at, null: false
|
||||
|
||||
t.index [ :key ], unique: true
|
||||
t.index [:key], unique: true
|
||||
end
|
||||
|
||||
create_table :active_storage_attachments do |t|
|
||||
@@ -20,7 +22,7 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddLockboxColumns < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :authentications, :access_token_ciphertext, :text
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddMetadataToSubmissions < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :submissions, :remote_ip, :string
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddSequentialIdToSubmissions < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :submissions, :sequential_id, :integer
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddTokenToAttachments < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :active_storage_attachments, :token, :string
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SorceryCore < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :users, :crypted_password, :string
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddAirtableSupport < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :forms, :airtable_app_key_ciphertext, :string
|
||||
|
||||
+67
-66
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# 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
|
||||
# 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.
|
||||
|
||||
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
|
||||
enable_extension "plpgsql"
|
||||
enable_extension 'plpgsql'
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "token"
|
||||
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 ["token"], name: "index_active_storage_attachments_on_token", unique: true
|
||||
create_table 'active_storage_attachments', force: :cascade do |t|
|
||||
t.string 'name', null: false
|
||||
t.string 'record_type', null: false
|
||||
t.bigint 'record_id', null: false
|
||||
t.bigint 'blob_id', null: false
|
||||
t.datetime 'created_at', null: false
|
||||
t.string 'token'
|
||||
t.index ['blob_id'], name: 'index_active_storage_attachments_on_blob_id'
|
||||
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
|
||||
end
|
||||
|
||||
create_table "active_storage_blobs", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "content_type"
|
||||
t.text "metadata"
|
||||
t.bigint "byte_size", null: false
|
||||
t.string "checksum", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||
create_table 'active_storage_blobs', force: :cascade do |t|
|
||||
t.string 'key', null: false
|
||||
t.string 'filename', null: false
|
||||
t.string 'content_type'
|
||||
t.text 'metadata'
|
||||
t.bigint 'byte_size', null: false
|
||||
t.string 'checksum', null: false
|
||||
t.datetime 'created_at', null: false
|
||||
t.index ['key'], name: 'index_active_storage_blobs_on_key', unique: true
|
||||
end
|
||||
|
||||
create_table "authentications", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.datetime "expires_at"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.text "access_token_ciphertext"
|
||||
t.text "refresh_token_ciphertext"
|
||||
t.string "provider"
|
||||
t.string "uid"
|
||||
create_table 'authentications', force: :cascade do |t|
|
||||
t.integer 'user_id'
|
||||
t.datetime 'expires_at'
|
||||
t.datetime 'created_at', precision: 6, null: false
|
||||
t.datetime 'updated_at', precision: 6, null: false
|
||||
t.text 'access_token_ciphertext'
|
||||
t.text 'refresh_token_ciphertext'
|
||||
t.string 'provider'
|
||||
t.string 'uid'
|
||||
end
|
||||
|
||||
create_table "forms", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.string "google_spreadsheet_id"
|
||||
t.string "title"
|
||||
t.string "token"
|
||||
t.string "thank_you_url"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.string "airtable_app_key_ciphertext"
|
||||
t.string "airtable_api_key_ciphertext"
|
||||
t.string "airtable_table"
|
||||
t.string "backend_name"
|
||||
create_table 'forms', force: :cascade do |t|
|
||||
t.integer 'user_id'
|
||||
t.string 'google_spreadsheet_id'
|
||||
t.string 'title'
|
||||
t.string 'token'
|
||||
t.string 'thank_you_url'
|
||||
t.datetime 'created_at', precision: 6, null: false
|
||||
t.datetime 'updated_at', precision: 6, null: false
|
||||
t.string 'airtable_app_key_ciphertext'
|
||||
t.string 'airtable_api_key_ciphertext'
|
||||
t.string 'airtable_table'
|
||||
t.string 'backend_name'
|
||||
end
|
||||
|
||||
create_table "submissions", force: :cascade do |t|
|
||||
t.integer "form_id"
|
||||
t.json "data", default: {}
|
||||
t.datetime "appended_at"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.string "remote_ip"
|
||||
t.string "referrer"
|
||||
t.integer "sequential_id"
|
||||
create_table 'submissions', force: :cascade do |t|
|
||||
t.integer 'form_id'
|
||||
t.json 'data', default: {}
|
||||
t.datetime 'appended_at'
|
||||
t.datetime 'created_at', precision: 6, null: false
|
||||
t.datetime 'updated_at', precision: 6, null: false
|
||||
t.string 'remote_ip'
|
||||
t.string 'referrer'
|
||||
t.integer 'sequential_id'
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "email"
|
||||
t.string "name"
|
||||
t.string "google_id"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.string "crypted_password"
|
||||
t.string "salt"
|
||||
t.string "magic_login_token"
|
||||
t.datetime "magic_login_token_expires_at"
|
||||
t.datetime "magic_login_email_sent_at"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["magic_login_token"], name: "index_users_on_magic_login_token"
|
||||
create_table 'users', force: :cascade do |t|
|
||||
t.string 'email'
|
||||
t.string 'name'
|
||||
t.string 'google_id'
|
||||
t.datetime 'created_at', precision: 6, null: false
|
||||
t.datetime 'updated_at', precision: 6, null: false
|
||||
t.string 'crypted_password'
|
||||
t.string 'salt'
|
||||
t.string 'magic_login_token'
|
||||
t.datetime 'magic_login_token_expires_at'
|
||||
t.datetime 'magic_login_email_sent_at'
|
||||
t.index ['email'], name: 'index_users_on_email', unique: true
|
||||
t.index ['magic_login_token'], name: 'index_users_on_magic_login_token'
|
||||
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
|
||||
@@ -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.
|
||||
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
|
||||
#
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'airrecord'
|
||||
module SpreadsheetBackends
|
||||
class Airtable
|
||||
|
||||
attr_accessor :form, :user
|
||||
|
||||
def initialize(form)
|
||||
@@ -16,8 +17,8 @@ module SpreadsheetBackends
|
||||
def append(data)
|
||||
result = table.create(data, typecast: true)
|
||||
result.id.present?
|
||||
rescue Airrecord::Error => e
|
||||
return false
|
||||
rescue Airrecord::Error
|
||||
false
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'google/apis/sheets_v4'
|
||||
module SpreadsheetBackends
|
||||
class GoogleSheets
|
||||
# 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
|
||||
|
||||
@@ -30,17 +32,17 @@ module SpreadsheetBackends
|
||||
def create
|
||||
sheets = Google::Apis::SheetsV4::SheetsService.new
|
||||
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)
|
||||
form.update(google_spreadsheet_id: spreadsheet.spreadsheet_id)
|
||||
end
|
||||
|
||||
def headers
|
||||
@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 values
|
||||
values[0].map(&:strip)
|
||||
values[0].map(&:strip)
|
||||
else
|
||||
[]
|
||||
end
|
||||
@@ -66,6 +68,5 @@ module SpreadsheetBackends
|
||||
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
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user