Merge branch 'master' into forms-index-page
This commit is contained in:
commit
5eff2042b3
6
Gemfile
6
Gemfile
@ -21,9 +21,13 @@ gem 'jbuilder'
|
|||||||
# Reduces boot times through caching; required in config/boot.rb
|
# Reduces boot times through caching; required in config/boot.rb
|
||||||
gem 'bootsnap', '>= 1.4.2', require: false
|
gem 'bootsnap', '>= 1.4.2', require: false
|
||||||
|
|
||||||
|
gem 'lockbox'
|
||||||
|
|
||||||
|
gem 'aws-sdk-s3', require: false
|
||||||
|
# gem 'airrecord'
|
||||||
gem 'google-api-client'
|
gem 'google-api-client'
|
||||||
gem 'rack-cors'
|
gem 'rack-cors'
|
||||||
gem "sentry-raven"
|
gem 'sentry-raven'
|
||||||
|
|
||||||
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
|
||||||
|
20
Gemfile.lock
20
Gemfile.lock
@ -58,6 +58,22 @@ GEM
|
|||||||
zeitwerk (~> 2.2)
|
zeitwerk (~> 2.2)
|
||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
aws-eventstream (1.0.3)
|
||||||
|
aws-partitions (1.263.0)
|
||||||
|
aws-sdk-core (3.89.1)
|
||||||
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
jmespath (~> 1.0)
|
||||||
|
aws-sdk-kms (1.27.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.71.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sdk-s3 (1.60.1)
|
||||||
|
aws-sdk-core (~> 3, >= 3.83.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sigv4 (1.1.0)
|
||||||
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
bootsnap (1.4.6)
|
bootsnap (1.4.6)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
@ -96,10 +112,12 @@ GEM
|
|||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jbuilder (2.10.0)
|
jbuilder (2.10.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
|
jmespath (1.4.0)
|
||||||
jwt (2.2.1)
|
jwt (2.2.1)
|
||||||
listen (3.2.1)
|
listen (3.2.1)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
|
lockbox (0.3.4)
|
||||||
loofah (2.5.0)
|
loofah (2.5.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
@ -218,12 +236,14 @@ PLATFORMS
|
|||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
aws-sdk-s3
|
||||||
bootsnap (>= 1.4.2)
|
bootsnap (>= 1.4.2)
|
||||||
byebug
|
byebug
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
google-api-client
|
google-api-client
|
||||||
jbuilder
|
jbuilder
|
||||||
listen
|
listen
|
||||||
|
lockbox
|
||||||
pg
|
pg
|
||||||
puma
|
puma
|
||||||
rack-cors
|
rack-cors
|
||||||
|
@ -28,6 +28,10 @@ To use the application the Google API client needs to be configured using the fo
|
|||||||
|
|
||||||
You can get those from the [Google APIs Dashboard](https://console.developers.google.com/apis/dashboard)
|
You can get those from the [Google APIs Dashboard](https://console.developers.google.com/apis/dashboard)
|
||||||
|
|
||||||
|
Additionally an encryption master key needs to be configured. [lockbox](https://github.com/ankane/lockbox) is used to encrypt sensitive data (e.g. access_token) at rest.
|
||||||
|
|
||||||
|
* LOCKBOX_MASTER_KEY
|
||||||
|
|
||||||
Store those in a `.env` file; see `env.example` for an example.
|
Store those in a `.env` file; see `env.example` for an example.
|
||||||
|
|
||||||
### Run the application
|
### Run the application
|
||||||
|
@ -14,22 +14,32 @@
|
|||||||
*= require_self
|
*= require_self
|
||||||
*/
|
*/
|
||||||
@import "bulma/sass/utilities/initial-variables";
|
@import "bulma/sass/utilities/initial-variables";
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Lobster&family=Comfortaa:wght@400;500;600;700&display=swap');
|
||||||
|
|
||||||
$family-sans-serif: "Helvetica",
|
$family-sans-serif: 'Roboto', sans-serif;
|
||||||
"Arial",
|
$family-secondary: 'Comfortaa', cursive;
|
||||||
sans-serif;
|
// // https://coolors.co/06aed5-086788-f0c808-fff1d0-dd1c1a
|
||||||
// https://coolors.co/06aed5-086788-f0c808-fff1d0-dd1c1a
|
// $blue: #083d77;
|
||||||
$blue: #083d77;
|
// $red: #dd1c1a;
|
||||||
$red: #dd1c1a;
|
// $orange: #ee964b;
|
||||||
$orange: #ee964b;
|
// $yellow: #f4d35e;
|
||||||
$yellow: #f4d35e;
|
// $light: #f5fafe; // #ebebd3;
|
||||||
$light: #f5fafe; // #ebebd3;
|
// $primary: $blue;
|
||||||
|
// $green: #007932; // hsl(141, 53%, 53%);
|
||||||
|
// $footer-background-color: $light;
|
||||||
|
|
||||||
|
$blue: #4c82fc;
|
||||||
$primary: $blue;
|
$primary: $blue;
|
||||||
$green: #007932; // hsl(141, 53%, 53%);
|
|
||||||
$footer-background-color: $light;
|
$text: $grey-dark;
|
||||||
|
$body-background-color: #FAFCFE;
|
||||||
|
|
||||||
@import 'bulma/bulma';
|
@import 'bulma/bulma';
|
||||||
|
|
||||||
.card-height{
|
.is-font-logo {
|
||||||
min-height: 200px;
|
font-family: 'Lobster', cursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
|
helper_method :current_user, :logged_in?
|
||||||
|
|
||||||
def require_login
|
def require_login
|
||||||
redirect_to login_url unless current_user.present?
|
redirect_to login_url unless current_user.present?
|
||||||
|
8
app/controllers/file_uploads_controller.rb
Normal file
8
app/controllers/file_uploads_controller.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class FileUploadsController < ApplicationController
|
||||||
|
def show
|
||||||
|
@form = Form.find_by!(token: params[:form_id])
|
||||||
|
@submission = @form.submissions.find(params[:submission_id])
|
||||||
|
@file_upload = @submission.files_attachments.find(params[:id])
|
||||||
|
redirect_to url_for(@file_upload)
|
||||||
|
end
|
||||||
|
end
|
@ -1,7 +1,7 @@
|
|||||||
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
|
||||||
before_action :require_login
|
before_action :require_login, except: [:form]
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@form = current_user.forms.build
|
@form = current_user.forms.build
|
||||||
@ -25,6 +25,10 @@ class FormsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def form
|
||||||
|
@form = Form.find_by!(token: params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def form_params
|
def form_params
|
||||||
|
@ -5,14 +5,18 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@form = Form.find_by!(token: params[:form_id])
|
@form = Form.find_by!(token: params[:form_id])
|
||||||
@submission = @form.submissions.build(data: data_params)
|
# create a new submission object. we need a persisted submission to be able to process
|
||||||
|
# potential the data - to be able to create URLs to uploads which is added as link to the table
|
||||||
|
@submission = @form.submissions.create(remote_ip: request.remote_ip, referrer: request.referer)
|
||||||
|
# processes the submitted data and saves the submission
|
||||||
|
@submission.process_data(data_params)
|
||||||
|
|
||||||
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 { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? }
|
||||||
format.json { render(json: { success: true, data: @submission.data }) }
|
format.json { render(json: { success: true, data: @submission.data }) }
|
||||||
else
|
else
|
||||||
format.html
|
format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? }
|
||||||
format.json { render(json: { error: @submission.errors }, status: 422) }
|
format.json { render(json: { error: @submission.errors }, status: 422) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
17
app/javascript/burger_menu.js
Normal file
17
app/javascript/burger_menu.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const $navbarBurgers = document.querySelectorAll('.navbar-burger');
|
||||||
|
// Check if there are any navbar burgers
|
||||||
|
if ($navbarBurgers.length > 0) {
|
||||||
|
// Add a click event on each of them
|
||||||
|
$navbarBurgers.forEach(el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
// Get the target from the "data-target" attribute
|
||||||
|
const target = el.dataset.target;
|
||||||
|
const $target = document.getElementById(target);
|
||||||
|
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||||
|
el.classList.toggle('is-active');
|
||||||
|
$target.classList.toggle('is-active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -8,10 +8,11 @@ require("turbolinks").start()
|
|||||||
require("@rails/activestorage").start()
|
require("@rails/activestorage").start()
|
||||||
require("channels")
|
require("channels")
|
||||||
|
|
||||||
|
require('burger_menu');
|
||||||
|
|
||||||
// Uncomment to copy all static images under ../images to the output folder and reference
|
// 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' %>)
|
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
|
||||||
// or the `imagePath` JavaScript helper below.
|
// or the `imagePath` JavaScript helper below.
|
||||||
//
|
//
|
||||||
// const images = require.context('../images', true)
|
// const images = require.context('../images', true)
|
||||||
// const imagePath = (name) => images(name, true)
|
// const imagePath = (name) => images(name, true)
|
16
app/jobs/submission_append_job.rb
Normal file
16
app/jobs/submission_append_job.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
class SubmissionAppendJob < ApplicationJob
|
||||||
|
queue_as :default
|
||||||
|
|
||||||
|
rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |exception|
|
||||||
|
submission_id = self.arguments.first
|
||||||
|
Rails.logger.error("AuthorizationError during SubmissionAppend: submission_id=#{submission_id}")
|
||||||
|
submission = Submission.find(submission_id)
|
||||||
|
submission.form.deactivate!('AuthorizationError')
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(*args)
|
||||||
|
submission_id = args.first
|
||||||
|
submission = Submission.find(submission_id)
|
||||||
|
submission.append_to_spreadsheet
|
||||||
|
end
|
||||||
|
end
|
@ -1,6 +1,9 @@
|
|||||||
class Authentication < ApplicationRecord
|
class Authentication < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
encrypts :access_token
|
||||||
|
encrypts :refresh_token
|
||||||
|
|
||||||
def expired?
|
def expired?
|
||||||
expires_at <= Time.current
|
expires_at <= Time.current
|
||||||
end
|
end
|
||||||
|
@ -11,6 +11,14 @@ class Form < ApplicationRecord
|
|||||||
|
|
||||||
validates_presence_of :title
|
validates_presence_of :title
|
||||||
|
|
||||||
|
def deactivate!(reason = nil)
|
||||||
|
self.user.deactivate!(reason)
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?
|
||||||
|
self.user.active?
|
||||||
|
end
|
||||||
|
|
||||||
def google_spreadsheet_url
|
def google_spreadsheet_url
|
||||||
"https://docs.google.com/spreadsheets/d/#{google_spreadsheet_id}/edit" if google_spreadsheet_id.present?
|
"https://docs.google.com/spreadsheets/d/#{google_spreadsheet_id}/edit" if google_spreadsheet_id.present?
|
||||||
end
|
end
|
||||||
|
@ -1,19 +1,37 @@
|
|||||||
class Submission < ApplicationRecord
|
class Submission < ApplicationRecord
|
||||||
belongs_to :form
|
belongs_to :form
|
||||||
after_create :append_to_spreadsheet
|
has_many_attached :files
|
||||||
validates_presence_of :data
|
|
||||||
|
|
||||||
def data=(value)
|
validates_presence_of :data, if: :appended_at?
|
||||||
sanitized_data = {}
|
|
||||||
value.each do |key, value|
|
def process_data(submitted_data)
|
||||||
sanitized_data[key] = submission_value(value)
|
processed_data = {}
|
||||||
|
submitted_data.each do |key, value|
|
||||||
|
processed_data[key] = submission_value_for(value)
|
||||||
end
|
end
|
||||||
write_attribute(:data, sanitized_data)
|
update_attribute(:data, processed_data)
|
||||||
|
SubmissionAppendJob.perform_later(self.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def submission_value(value)
|
def submission_value_for(value)
|
||||||
if value.to_s.downcase == 'tinyforms_now'
|
case value
|
||||||
|
when Array
|
||||||
|
value.join(', ')
|
||||||
|
when Hash
|
||||||
|
JSON.dump(value)
|
||||||
|
when 'tinyforms_now'
|
||||||
Time.now.utc.to_formatted_s(:rfc822)
|
Time.now.utc.to_formatted_s(:rfc822)
|
||||||
|
when ActionDispatch::Http::UploadedFile
|
||||||
|
# manually create the ActiveStorage attachment because we need the ID of the Attachment to create the URL
|
||||||
|
# first the file needs to be uplaoded then we can create an Attachment
|
||||||
|
# The CreateOne mainly handles the uplaod and the creation of the blob for us
|
||||||
|
# `files` is the name from `has_many_attached :files`
|
||||||
|
create_one = ActiveStorage::Attached::Changes::CreateOne.new('files', self, value)
|
||||||
|
create_one.upload
|
||||||
|
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.id, host: DEFAULT_HOST)
|
||||||
else
|
else
|
||||||
value.to_s
|
value.to_s
|
||||||
end
|
end
|
||||||
|
@ -8,7 +8,12 @@ class User < ApplicationRecord
|
|||||||
user_info = oauth.get_userinfo
|
user_info = oauth.get_userinfo
|
||||||
|
|
||||||
if user = User.find_by(google_id: user_info.id)
|
if user = User.find_by(google_id: user_info.id)
|
||||||
return user, user.authentications.last
|
authentication = user.authentications.last
|
||||||
|
authentication.access_token = auth_client.access_token if auth_client.access_token.present?
|
||||||
|
authentication.refresh_token = auth_client.refresh_token if auth_client.refresh_token.present?
|
||||||
|
authentication.expires_at = Time.at(auth_client.expires_at) if auth_client.expires_at.present?
|
||||||
|
authentication.save
|
||||||
|
return user, authentication
|
||||||
else
|
else
|
||||||
user = User.create(name: user_info.name, email: user_info.email, google_id: user_info.id)
|
user = User.create(name: user_info.name, email: user_info.email, google_id: user_info.id)
|
||||||
authentication = user.authentications.create(
|
authentication = user.authentications.create(
|
||||||
@ -20,6 +25,15 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def active?
|
||||||
|
authentications.last.present? && !authentications.last.expired?
|
||||||
|
end
|
||||||
|
|
||||||
def google_authorization
|
def google_authorization
|
||||||
authentications.last.google_authorization
|
authentications.last.google_authorization
|
||||||
end
|
end
|
||||||
|
12
app/views/forms/form.html.erb
Normal file
12
app/views/forms/form.html.erb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<h1><%= @form.title %></h1>
|
||||||
|
|
||||||
|
<%= form_with url: submission_url(@form), action: 'post', authenticity_token: false, local: true, html: { enctype: 'multipart/form-data' } do %>
|
||||||
|
<% @form.header_values.each do |header| %>
|
||||||
|
<p>
|
||||||
|
<label><%= header %>
|
||||||
|
<%= text_field_tag header %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= submit_tag 'Send', name: nil %>
|
||||||
|
<% end %>
|
@ -1,3 +1,12 @@
|
|||||||
<h1>Welcome</h1>
|
<section class="hero is-medium is-dark is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
<%= link_to "Login", login_url %>
|
<div class="container">
|
||||||
|
<h1 class="title is-family-secondary has-text-weight-bold">
|
||||||
|
Welcome
|
||||||
|
</h1>
|
||||||
|
<p class="subtitle is-size-6 has-text-white">
|
||||||
|
Generate forms instantly
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
<<<<<<< HEAD
|
||||||
<head>
|
<head>
|
||||||
<title>Tinyform</title>
|
<title>Tinyform</title>
|
||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
@ -15,3 +16,64 @@
|
|||||||
<script src="https://kit.fontawesome.com/221ac0e2fd.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/221ac0e2fd.js" crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
=======
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Tinyform</title>
|
||||||
|
<%= csrf_meta_tags %>
|
||||||
|
<%= csp_meta_tag %>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||||
|
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Wrapper -->
|
||||||
|
<div id="wrapper" class="has-text-centered-mobile">
|
||||||
|
|
||||||
|
<!-- Hero -->
|
||||||
|
<section class="hero is-medium">
|
||||||
|
<div class="hero-head">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item is-size-4 has-text-black has-text-weight-bold is-font-logo" href="/">
|
||||||
|
TinyForms
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||||
|
data-target="navbar">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="navbar" class="navbar-menu">
|
||||||
|
<div class="navbar-end">
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="buttons">
|
||||||
|
<% if !logged_in? -%>
|
||||||
|
<%= link_to "Login", login_url, { :class => "button is-primary"} %>
|
||||||
|
<% else -%>
|
||||||
|
<%= link_to "Logout", logout_url, { :class => "button is-light"} %>
|
||||||
|
<% end -%>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<%= yield %>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://kit.fontawesome.com/221ac0e2fd.js" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
>>>>>>> master
|
||||||
|
@ -36,7 +36,7 @@ Rails.application.configure do
|
|||||||
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
|
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
|
||||||
|
|
||||||
# Store uploaded files on the local file system (see config/storage.yml for options).
|
# Store uploaded files on the local file system (see config/storage.yml for options).
|
||||||
config.active_storage.service = :local
|
config.active_storage.service = :amazon
|
||||||
|
|
||||||
# Mount Action Cable outside main process or domain.
|
# Mount Action Cable outside main process or domain.
|
||||||
# config.action_cable.mount_path = nil
|
# config.action_cable.mount_path = nil
|
||||||
|
1
config/initializers/host.rb
Normal file
1
config/initializers/host.rb
Normal file
@ -0,0 +1 @@
|
|||||||
|
DEFAULT_HOST = ENV['DEFAULT_HOST'] || 'localhost:3000'
|
@ -2,10 +2,16 @@ 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
|
||||||
|
|
||||||
resources :forms do
|
resources :forms do
|
||||||
|
member { get :form }
|
||||||
resources :submissions
|
resources :submissions
|
||||||
end
|
end
|
||||||
|
# short link for submission file uploads
|
||||||
|
get '/s/:form_id/:submission_id/:id' => 'file_uploads#show', as: :file_upload
|
||||||
|
|
||||||
|
# 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 '/login' => 'sessions#new', as: :login
|
get '/login' => 'sessions#new', as: :login
|
||||||
get '/logout' => 'sessions#destroy', as: :logout
|
get '/logout' => 'sessions#destroy', as: :logout
|
||||||
|
@ -6,13 +6,13 @@ local:
|
|||||||
service: Disk
|
service: Disk
|
||||||
root: <%= Rails.root.join("storage") %>
|
root: <%= Rails.root.join("storage") %>
|
||||||
|
|
||||||
# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
|
# access keys and region are configured using environment variables
|
||||||
# amazon:
|
amazon:
|
||||||
# service: S3
|
service: S3
|
||||||
|
bucket: <%= ENV['AWS_S3_BUCKET'] %>
|
||||||
# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
|
# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
|
||||||
# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
|
# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
|
||||||
# region: us-east-1
|
# region: ENV['AWS_REGION']
|
||||||
# bucket: your_own_bucket
|
|
||||||
|
|
||||||
# Remember not to checkin your GCS keyfile to a repository
|
# Remember not to checkin your GCS keyfile to a repository
|
||||||
# google:
|
# google:
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# This migration comes from active_storage (originally 20170806125915)
|
||||||
|
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :active_storage_blobs 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 ], unique: true
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :active_storage_attachments do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.references :record, null: false, polymorphic: true, index: false
|
||||||
|
t.references :blob, 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.foreign_key :active_storage_blobs, column: :blob_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
8
db/migrate/20200408212150_add_lockbox_columns.rb
Normal file
8
db/migrate/20200408212150_add_lockbox_columns.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class AddLockboxColumns < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
add_column :authentications, :access_token_ciphertext, :text
|
||||||
|
add_column :authentications, :refresh_token_ciphertext, :text
|
||||||
|
remove_column :authentications, :access_token
|
||||||
|
remove_column :authentications, :refresh_token
|
||||||
|
end
|
||||||
|
end
|
6
db/migrate/20200409001610_add_metadata_to_submissions.rb
Normal file
6
db/migrate/20200409001610_add_metadata_to_submissions.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class AddMetadataToSubmissions < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
add_column :submissions, :remote_ip, :string
|
||||||
|
add_column :submissions, :referrer, :string
|
||||||
|
end
|
||||||
|
end
|
30
db/schema.rb
30
db/schema.rb
@ -10,18 +10,39 @@
|
|||||||
#
|
#
|
||||||
# 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_05_161905) do
|
ActiveRecord::Schema.define(version: 2020_04_09_001610) 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|
|
||||||
|
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.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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
create_table "authentications", force: :cascade do |t|
|
create_table "authentications", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.string "access_token"
|
|
||||||
t.string "refresh_token"
|
|
||||||
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 "refresh_token_ciphertext"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "forms", force: :cascade do |t|
|
create_table "forms", force: :cascade do |t|
|
||||||
@ -40,6 +61,8 @@ ActiveRecord::Schema.define(version: 2020_04_05_161905) do
|
|||||||
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 "referrer"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
@ -50,4 +73,5 @@ ActiveRecord::Schema.define(version: 2020_04_05_161905) do
|
|||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
GOOGLE_CLIENT_ID=clientid
|
GOOGLE_CLIENT_ID=clientid
|
||||||
GOOGLE_CLIENT_SECRET=secret
|
GOOGLE_CLIENT_SECRET=secret
|
||||||
GOOGLE_PROJECT_ID=projectid
|
GOOGLE_PROJECT_ID=projectid
|
||||||
|
LOCKBOX_MASTER_KEY=f7b18b63d3f7ec48fa78bab327cdf81b0969020f70dc16947b14572cde3e2b7d
|
||||||
|
30
form.html
Normal file
30
form.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Tinyforms</title>
|
||||||
|
<meta name="csrf-param" content="authenticity_token" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Test form</h1>
|
||||||
|
<form method="post" action="" enctype="multipart/form-data">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label>Text</label>
|
||||||
|
<input type="text" name="text">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label>Array</label>
|
||||||
|
<input type="checkbox" name="array[]" value="1">
|
||||||
|
<input type="checkbox" name="array[]" value="2">
|
||||||
|
<input type="checkbox" name="array[]" value="3">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label>File</label>
|
||||||
|
<input type="file" name="file">
|
||||||
|
</p>
|
||||||
|
<input type="submit" value="senden">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -7,6 +7,7 @@
|
|||||||
"@rails/ujs": "^6.0.0",
|
"@rails/ujs": "^6.0.0",
|
||||||
"@rails/webpacker": "4.2.2",
|
"@rails/webpacker": "4.2.2",
|
||||||
"bulma": "^0.8.1",
|
"bulma": "^0.8.1",
|
||||||
|
"bulma-helpers": "^0.3.10",
|
||||||
"turbolinks": "^5.2.0"
|
"turbolinks": "^5.2.0"
|
||||||
},
|
},
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
BIN
tinyforms_development
Normal file
BIN
tinyforms_development
Normal file
Binary file not shown.
@ -1540,6 +1540,11 @@ builtin-status-codes@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||||
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
|
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
|
||||||
|
|
||||||
|
bulma-helpers@^0.3.10:
|
||||||
|
version "0.3.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/bulma-helpers/-/bulma-helpers-0.3.10.tgz#a0ab518b44343bb708339ade721f7355d159a547"
|
||||||
|
integrity sha512-dgJB8LreVzAHJfcbYUUONA0oo/cdWxhFt1b/DlmTLaukYTjkdM5GP9A7DRyssjuRhcozIqzTm//BLLR313Xw3Q==
|
||||||
|
|
||||||
bulma@^0.8.1:
|
bulma@^0.8.1:
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.8.1.tgz#a5feacb703b73a87fdeae4f0d12317d62fc1d301"
|
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.8.1.tgz#a5feacb703b73a87fdeae4f0d12317d62fc1d301"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user