From 7e4280d4512de817be34a5991c7f1401bdf4db65 Mon Sep 17 00:00:00 2001 From: Yannick Date: Tue, 7 Apr 2020 20:33:47 +0200 Subject: [PATCH 01/27] Add application layout styling Added application navbar and the page background styling --- app/assets/stylesheets/application.css.scss | 31 ++++++++---- app/views/home/index.html.erb | 6 ++- app/views/layouts/application.html.erb | 54 +++++++++++++++++---- 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 0ac9955..463abf5 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -14,18 +14,29 @@ *= require_self */ @import "bulma/sass/utilities/initial-variables"; +@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Lobster&display=swap'); $family-sans-serif: "Helvetica", "Arial", sans-serif; -// https://coolors.co/06aed5-086788-f0c808-fff1d0-dd1c1a -$blue: #083d77; -$red: #dd1c1a; -$orange: #ee964b; -$yellow: #f4d35e; -$light: #f5fafe; // #ebebd3; -$primary: $blue; -$green: #007932; // hsl(141, 53%, 53%); -$footer-background-color: $light; +$family-primary: 'Quicksand', sans-serif; -@import 'bulma/bulma'; \ No newline at end of file +// // https://coolors.co/06aed5-086788-f0c808-fff1d0-dd1c1a +// $blue: #083d77; +// $red: #dd1c1a; +// $orange: #ee964b; +// $yellow: #f4d35e; +// $light: #f5fafe; // #ebebd3; +// $primary: $blue; +// $green: #007932; // hsl(141, 53%, 53%); +// $footer-background-color: $light; + +@import 'bulma/bulma'; + +.is-font-logo { + font-family: 'Lobster', cursive; +} + +body { + min-height: 100vh; +} \ No newline at end of file diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 2c2945f..93d8579 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,3 +1,5 @@ -

Welcome

+
+

Welcome

-<%= link_to "Login", login_url %> + <%= link_to "Login", login_url %> +
\ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index d2e6710..1ad564b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,15 +1,49 @@ - - Tinyform - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> - <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> - + + Tinyform + <%= csrf_meta_tags %> + <%= csp_meta_tag %> - + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> + + + +
+
+ +
+
+
<%= yield %> - - +
+ + + \ No newline at end of file From ab81cf9d5c41528e260198ad572fd1bac0d4153f Mon Sep 17 00:00:00 2001 From: Yannick Date: Tue, 7 Apr 2020 20:52:45 +0200 Subject: [PATCH 02/27] Added logout button Added logged_in helper to allow us to check whether a user is logged in to show the appropriate button --- app/controllers/application_controller.rb | 1 + app/views/layouts/application.html.erb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e288f36..1be493f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,5 @@ class ApplicationController < ActionController::Base + helper_method :current_user, :logged_in? def require_login redirect_to login_url unless current_user.present? diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 1ad564b..b48478b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -33,7 +33,11 @@ From ff442443084733b82235571c545e8d3fb16353ff Mon Sep 17 00:00:00 2001 From: Yannick Date: Tue, 7 Apr 2020 21:28:03 +0200 Subject: [PATCH 03/27] Add new fonts for the platform --- app/assets/stylesheets/application.css.scss | 9 +++------ app/views/home/index.html.erb | 17 ++++++++++++----- app/views/layouts/application.html.erb | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 463abf5..3f248e1 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -14,13 +14,10 @@ *= require_self */ @import "bulma/sass/utilities/initial-variables"; -@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Lobster&display=swap'); - -$family-sans-serif: "Helvetica", -"Arial", -sans-serif; -$family-primary: 'Quicksand', sans-serif; +@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: 'Roboto', sans-serif; +$family-secondary: 'Comfortaa', cursive; // // https://coolors.co/06aed5-086788-f0c808-fff1d0-dd1c1a // $blue: #083d77; // $red: #dd1c1a; diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 93d8579..5be9a9b 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,5 +1,12 @@ -
-

Welcome

- - <%= link_to "Login", login_url %> -
\ No newline at end of file +
+
+
+

+ Welcome +

+

+ Generate forms instantly +

+
+
+
\ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index b48478b..d3a893c 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -45,7 +45,7 @@ -
+
<%= yield %>
From c964c15196b7c6c3fc4dc5a37ffc6803d023a6e3 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Wed, 8 Apr 2020 00:50:35 +0200 Subject: [PATCH 04/27] Form value handling for Arrays and Hashes This processes Array and Hash values and converts those to strings that can be appended to the spreadsheet --- app/models/submission.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/submission.rb b/app/models/submission.rb index db3d510..2118688 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -12,8 +12,15 @@ class Submission < ApplicationRecord end def submission_value(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) + when ActionDispatch::Http::UploadedFile + '' else value.to_s end From 4550dc214ebf0a8b04a464fb27fb6eb7a426740e Mon Sep 17 00:00:00 2001 From: Yannick Date: Wed, 8 Apr 2020 13:59:08 +0200 Subject: [PATCH 05/27] Add bulma css helpers --- app/assets/stylesheets/application.css.scss | 3 ++- app/views/home/index.html.erb | 2 +- app/views/layouts/application.html.erb | 2 +- package.json | 1 + yarn.lock | 5 +++++ 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 3f248e1..3ad7b6e 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -36,4 +36,5 @@ $family-secondary: 'Comfortaa', cursive; body { min-height: 100vh; -} \ No newline at end of file +} +@import 'bulma-helpers/bulma-helpers' \ No newline at end of file diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 5be9a9b..75d1488 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,6 +1,6 @@
-
+

Welcome

diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index d3a893c..621f4d8 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -13,7 +13,7 @@
-
+
+ +
+ <%= yield %> +
-
- <%= yield %> -
\ No newline at end of file From 3c5e4ca0807ec20cd09b7c3d9cb24672d213b259 Mon Sep 17 00:00:00 2001 From: Yannick Date: Wed, 8 Apr 2020 19:48:08 +0200 Subject: [PATCH 10/27] Navbar restructuring --- app/views/layouts/application.html.erb | 50 +++++++++++++++++++------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index feb59bb..7de3e64 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -21,22 +21,19 @@
+ + + \ No newline at end of file From 73ccddee949353d79ff6004605d50d67d0de8a4d Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 00:41:54 +0200 Subject: [PATCH 11/27] Encrypt access_token and refresh_token --- Gemfile | 5 ++++- Gemfile.lock | 2 ++ app/models/authentication.rb | 3 +++ db/migrate/20200408212150_add_lockbox_columns.rb | 8 ++++++++ db/schema.rb | 2 +- env.example | 1 + 6 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20200408212150_add_lockbox_columns.rb diff --git a/Gemfile b/Gemfile index ccc2913..cc5430e 100644 --- a/Gemfile +++ b/Gemfile @@ -21,9 +21,12 @@ gem 'jbuilder' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.4.2', require: false +gem 'lockbox' + +# gem 'airrecord' gem 'google-api-client' gem 'rack-cors' -gem "sentry-raven" +gem 'sentry-raven' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 01fe03f..2cc091d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,6 +100,7 @@ GEM listen (3.2.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + lockbox (0.3.4) loofah (2.5.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -224,6 +225,7 @@ DEPENDENCIES google-api-client jbuilder listen + lockbox pg puma rack-cors diff --git a/app/models/authentication.rb b/app/models/authentication.rb index 4195dc4..58fe264 100644 --- a/app/models/authentication.rb +++ b/app/models/authentication.rb @@ -1,6 +1,9 @@ class Authentication < ApplicationRecord belongs_to :user + encrypts :access_token + encrypts :refresh_token + def expired? expires_at <= Time.current end diff --git a/db/migrate/20200408212150_add_lockbox_columns.rb b/db/migrate/20200408212150_add_lockbox_columns.rb new file mode 100644 index 0000000..05d0e49 --- /dev/null +++ b/db/migrate/20200408212150_add_lockbox_columns.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index b1176f2..359fb4f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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_08_212150) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/env.example b/env.example index 3ace5a2..70b5e82 100644 --- a/env.example +++ b/env.example @@ -1,3 +1,4 @@ GOOGLE_CLIENT_ID=clientid GOOGLE_CLIENT_SECRET=secret GOOGLE_PROJECT_ID=projectid +LOCKBOX_MASTER_KEY=f7b18b63d3f7ec48fa78bab327cdf81b0969020f70dc16947b14572cde3e2b7d From 10d80c654863c309dfd6f7e5716f006d64c56cd1 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 11:02:07 +0200 Subject: [PATCH 12/27] Support file uploads This allows submissions to store file uploads --- app/controllers/file_uploads_controller.rb | 8 +++++ app/controllers/submissions_controller.rb | 8 +++-- app/jobs/submission_append_job.rb | 8 +++++ app/models/submission.rb | 30 +++++++++++++------ config/initializers/host.rb | 1 + config/routes.rb | 3 ++ ...te_active_storage_tables.active_storage.rb | 27 +++++++++++++++++ ...00409001610_add_metadata_to_submissions.rb | 6 ++++ db/schema.rb | 30 +++++++++++++++++-- 9 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 app/controllers/file_uploads_controller.rb create mode 100644 app/jobs/submission_append_job.rb create mode 100644 config/initializers/host.rb create mode 100644 db/migrate/20200406221804_create_active_storage_tables.active_storage.rb create mode 100644 db/migrate/20200409001610_add_metadata_to_submissions.rb diff --git a/app/controllers/file_uploads_controller.rb b/app/controllers/file_uploads_controller.rb new file mode 100644 index 0000000..8ad47cc --- /dev/null +++ b/app/controllers/file_uploads_controller.rb @@ -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 diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 66412fa..518a3eb 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -5,14 +5,18 @@ class SubmissionsController < ApplicationController def create @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| if @submission.save format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? } format.json { render(json: { success: true, data: @submission.data }) } 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) } end end diff --git a/app/jobs/submission_append_job.rb b/app/jobs/submission_append_job.rb new file mode 100644 index 0000000..fe55176 --- /dev/null +++ b/app/jobs/submission_append_job.rb @@ -0,0 +1,8 @@ +class SubmissionAppendJob < ApplicationJob + queue_as :default + + def perform(submission_id) + submission = Submission.find(submission_id) + submission.append_to_spreadsheet + end +end diff --git a/app/models/submission.rb b/app/models/submission.rb index 2118688..13f03d1 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -1,17 +1,20 @@ class Submission < ApplicationRecord belongs_to :form - after_create :append_to_spreadsheet - validates_presence_of :data + has_many :file_uploads, dependent: :destroy + has_many_attached :files - def data=(value) - sanitized_data = {} - value.each do |key, value| - sanitized_data[key] = submission_value(value) + validates_presence_of :data, if: :appended_at? + + def process_data(submitted_data) + processed_data = {} + submitted_data.each do |key, value| + processed_data[key] = submission_value_for(value) end - write_attribute(:data, sanitized_data) + update_attribute(:data, processed_data) + SubmissionAppendJob.perform_later(self.id) end - def submission_value(value) + def submission_value_for(value) case value when Array value.join(', ') @@ -20,7 +23,16 @@ class Submission < ApplicationRecord when 'tinyforms_now' 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 value.to_s end diff --git a/config/initializers/host.rb b/config/initializers/host.rb new file mode 100644 index 0000000..e4d2eaa --- /dev/null +++ b/config/initializers/host.rb @@ -0,0 +1 @@ +DEFAULT_HOST = ENV['DEFAULT_HOST'] || 'localhost:3000' diff --git a/config/routes.rb b/config/routes.rb index 39fe148..e647883 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,10 @@ Rails.application.routes.draw do resources :forms do resources :submissions 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 get '/login' => 'sessions#new', as: :login diff --git a/db/migrate/20200406221804_create_active_storage_tables.active_storage.rb b/db/migrate/20200406221804_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..0b2ce25 --- /dev/null +++ b/db/migrate/20200406221804_create_active_storage_tables.active_storage.rb @@ -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 diff --git a/db/migrate/20200409001610_add_metadata_to_submissions.rb b/db/migrate/20200409001610_add_metadata_to_submissions.rb new file mode 100644 index 0000000..cb9faf4 --- /dev/null +++ b/db/migrate/20200409001610_add_metadata_to_submissions.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 359fb4f..4e875ea 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,18 +10,39 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_04_08_212150) do +ActiveRecord::Schema.define(version: 2020_04_09_001610) do # These are extensions that must be enabled in order to support this database 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| t.integer "user_id" - t.string "access_token" - t.string "refresh_token" 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" end create_table "forms", force: :cascade do |t| @@ -40,6 +61,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_212150) do 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" end create_table "users", force: :cascade do |t| @@ -50,4 +73,5 @@ ActiveRecord::Schema.define(version: 2020_04_08_212150) do t.datetime "updated_at", precision: 6, null: false end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" end From 94966a9933bc4007ae249857fa0e5af1bd0e38af Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 11:51:54 +0200 Subject: [PATCH 13/27] Handle authentication errors when appending data This catches exceptions that can happen when an authentication is expired or removed by the user on google's side. --- app/jobs/submission_append_job.rb | 16 ++++++++++++++-- app/models/form.rb | 8 ++++++++ app/models/user.rb | 9 +++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/jobs/submission_append_job.rb b/app/jobs/submission_append_job.rb index fe55176..0403000 100644 --- a/app/jobs/submission_append_job.rb +++ b/app/jobs/submission_append_job.rb @@ -1,8 +1,20 @@ class SubmissionAppendJob < ApplicationJob queue_as :default - def perform(submission_id) + 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.append_to_spreadsheet + submission.form.deactivate!('AuthorizationError') + end + + def perform(*args) + submission_id = args.first + submission = Submission.find(submission_id) + if submission.form.active? + submission.append_to_spreadsheet + else + Rails.logger.error("Inactive form: submission_id=#{submission_id} form_id=#{submission.form_id}") + end end end diff --git a/app/models/form.rb b/app/models/form.rb index 9752094..c70d3bd 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -11,6 +11,14 @@ class Form < ApplicationRecord validates_presence_of :title + def deactivate!(reason = nil) + self.user.deactivate!(reason) + end + + def active? + self.user.active? + end + def google_spreadsheet_url "https://docs.google.com/spreadsheets/d/#{google_spreadsheet_id}/edit" if google_spreadsheet_id.present? end diff --git a/app/models/user.rb b/app/models/user.rb index 83bb70e..2918dc3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -25,6 +25,15 @@ class User < ApplicationRecord 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 authentications.last.google_authorization end From c1b9f8e7010e50ee60f51174390de16585a60565 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 11:53:04 +0200 Subject: [PATCH 14/27] Add test form in development mode This helps to test submissions in development mode --- app/controllers/forms_controller.rb | 8 +++++++- app/views/forms/form.html.erb | 22 ++++++++++++++++++++++ config/routes.rb | 3 +++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 app/views/forms/form.html.erb diff --git a/app/controllers/forms_controller.rb b/app/controllers/forms_controller.rb index a3d57c0..924395a 100644 --- a/app/controllers/forms_controller.rb +++ b/app/controllers/forms_controller.rb @@ -1,7 +1,7 @@ require 'google/apis/sheets_v4' require 'google/api_client/client_secrets' class FormsController < ApplicationController - before_action :require_login + before_action :require_login, except: [:form] def new @form = current_user.forms.build @@ -25,6 +25,12 @@ class FormsController < ApplicationController end end + if Rails.env.development? + def form + @form = Form.find_by!(token: params[:id]) + end + end + private def form_params diff --git a/app/views/forms/form.html.erb b/app/views/forms/form.html.erb new file mode 100644 index 0000000..22d1502 --- /dev/null +++ b/app/views/forms/form.html.erb @@ -0,0 +1,22 @@ +

<%= @form.title %>

+

+ <%= link_to 'Google spreadsheet', @form.google_spreadsheet_url %> +

+
+ +

+ + +

+

+ + + + +

+

+ + +

+ +
diff --git a/config/routes.rb b/config/routes.rb index e647883..7e54f35 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,9 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html resources :forms do + if Rails.env.development? + member { get :form } + end resources :submissions end # short link for submission file uploads From 829f534afd71f3aa4c7736bc95e729559b10f47b Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 12:24:18 +0200 Subject: [PATCH 15/27] Configure activestorage for production access keys and region are configured using environment variables --- Gemfile | 1 + Gemfile.lock | 18 ++++++++++++++++++ config/environments/production.rb | 2 +- config/storage.yml | 10 +++++----- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index cc5430e..5de7f90 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,7 @@ gem 'bootsnap', '>= 1.4.2', require: false gem 'lockbox' +gem 'aws-sdk-s3', require: false # gem 'airrecord' gem 'google-api-client' gem 'rack-cors' diff --git a/Gemfile.lock b/Gemfile.lock index 2cc091d..eabe759 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,6 +58,22 @@ GEM zeitwerk (~> 2.2) addressable (2.7.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) msgpack (~> 1.0) builder (3.2.4) @@ -96,6 +112,7 @@ GEM concurrent-ruby (~> 1.0) jbuilder (2.10.0) activesupport (>= 5.0.0) + jmespath (1.4.0) jwt (2.2.1) listen (3.2.1) rb-fsevent (~> 0.10, >= 0.10.3) @@ -219,6 +236,7 @@ PLATFORMS ruby DEPENDENCIES + aws-sdk-s3 bootsnap (>= 1.4.2) byebug dotenv-rails diff --git a/config/environments/production.rb b/config/environments/production.rb index 6ac7045..51f2525 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -36,7 +36,7 @@ Rails.application.configure do # 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). - config.active_storage.service = :local + config.active_storage.service = :amazon # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil diff --git a/config/storage.yml b/config/storage.yml index d32f76e..b79ac90 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -6,13 +6,13 @@ local: service: Disk root: <%= Rails.root.join("storage") %> -# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) -# amazon: -# service: S3 +# access keys and region are configured using environment variables +amazon: + service: S3 + bucket: ENV['AWS_S3_BUCKET'] # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> -# region: us-east-1 -# bucket: your_own_bucket +# region: ENV['AWS_REGION'] # Remember not to checkin your GCS keyfile to a repository # google: From 9c9ed88fa065712c5814c7dbb92c386f66d4587b Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 13:39:04 +0200 Subject: [PATCH 16/27] That should not be there --- app/models/submission.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/submission.rb b/app/models/submission.rb index 13f03d1..899469c 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -1,6 +1,5 @@ class Submission < ApplicationRecord belongs_to :form - has_many :file_uploads, dependent: :destroy has_many_attached :files validates_presence_of :data, if: :appended_at? From d485b1e62e51eca5245ea2d020fa1ec256d7c7eb Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 14:09:31 +0200 Subject: [PATCH 17/27] Fix amazon storage config --- config/storage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/storage.yml b/config/storage.yml index b79ac90..64a4621 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -9,7 +9,7 @@ local: # access keys and region are configured using environment variables amazon: service: S3 - bucket: ENV['AWS_S3_BUCKET'] + bucket: <%= ENV['AWS_S3_BUCKET'] %> # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> # region: ENV['AWS_REGION'] From fd9eddedfc43f8c64433616bedc7317a685de0b2 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 14:15:19 +0200 Subject: [PATCH 18/27] Experiment with a simple form builder --- app/controllers/forms_controller.rb | 6 ++---- app/views/forms/form.html.erb | 30 ++++++++++------------------- config/routes.rb | 4 +--- 3 files changed, 13 insertions(+), 27 deletions(-) diff --git a/app/controllers/forms_controller.rb b/app/controllers/forms_controller.rb index 924395a..92c2ece 100644 --- a/app/controllers/forms_controller.rb +++ b/app/controllers/forms_controller.rb @@ -25,10 +25,8 @@ class FormsController < ApplicationController end end - if Rails.env.development? - def form - @form = Form.find_by!(token: params[:id]) - end + def form + @form = Form.find_by!(token: params[:id]) end private diff --git a/app/views/forms/form.html.erb b/app/views/forms/form.html.erb index 22d1502..b3f6ebb 100644 --- a/app/views/forms/form.html.erb +++ b/app/views/forms/form.html.erb @@ -1,22 +1,12 @@

<%= @form.title %>

-

- <%= link_to 'Google spreadsheet', @form.google_spreadsheet_url %> -

-
-

- - -

-

- - - - -

-

- - -

- -
+<%= 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| %> +

+

+ <% end %> + + <%= submit_tag 'Send', name: nil %> +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 7e54f35..a939654 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,9 +2,7 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html resources :forms do - if Rails.env.development? - member { get :form } - end + member { get :form } resources :submissions end # short link for submission file uploads From f19d7bb36bbd067cd6c5c994772e7823f2876bc9 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 14:15:53 +0200 Subject: [PATCH 19/27] Static HTML form for testing --- form.html | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 form.html diff --git a/form.html b/form.html new file mode 100644 index 0000000..4611f99 --- /dev/null +++ b/form.html @@ -0,0 +1,30 @@ + + + + Tinyforms + + + + +

Test form

+
+ +

+ + +

+

+ + + + +

+

+ + +

+ +
+ + + From b09fe4c12deccbf3401ab7f1d35a8e857440bdff Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 14:20:35 +0200 Subject: [PATCH 20/27] Use similar URL for form submitter as for the submission URL --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index a939654..9d13189 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,7 @@ Rails.application.routes.draw do # form post url to save new submissions post '/s/:form_id' => 'submissions#create', as: :submission + post '/s/:form_id/form' => 'forms#form', as: :form_submitter get '/login' => 'sessions#new', as: :login get '/logout' => 'sessions#destroy', as: :logout From 9b3669ed296b2b65366e4f9e0582fc6e79491ebc Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 16:06:08 +0200 Subject: [PATCH 21/27] Add note about lockbox config --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 3868adf..71621b9 100644 --- a/README.md +++ b/README.md @@ -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) +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. ### Run the application From 957473430435683a8e7e0cce3dba5afdd17d4504 Mon Sep 17 00:00:00 2001 From: Yannick Date: Thu, 9 Apr 2020 17:15:47 +0200 Subject: [PATCH 22/27] Remove javascript snippets from application layout Moved the javascript codes into the own application.js file --- app/javascript/packs/application.js | 27 ++++++++++++++++++++++++ app/views/layouts/application.html.erb | 29 -------------------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 9cd55d4..472e587 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -15,3 +15,30 @@ require("channels") // // const images = require.context('../images', true) // const imagePath = (name) => images(name, true) + + +document.addEventListener('DOMContentLoaded', () => { + + // Get all "navbar-burger" elements + const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); + + // 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'); + + }); + }); + } + + }); \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7de3e64..b421579 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -54,35 +54,6 @@ <%= yield %> - - - \ No newline at end of file From 6bfe8fb485d47e03c2235beda77fcda157885e45 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 14:26:13 +0200 Subject: [PATCH 23/27] Fix routes --- config/routes.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 9d13189..5bc2fc6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,7 +10,8 @@ Rails.application.routes.draw do # form post url to save new submissions post '/s/:form_id' => 'submissions#create', as: :submission - post '/s/:form_id/form' => 'forms#form', as: :form_submitter + # short URL for form page + get '/s/:id/form' => 'forms#form', as: :form_submitter get '/login' => 'sessions#new', as: :login get '/logout' => 'sessions#destroy', as: :logout From 17d37edf0b6744c922702f533c6f353893c8ab51 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 14:46:50 +0200 Subject: [PATCH 24/27] Always try to append the data to the spreadsheet Authentication errors will be caught an logged, but it should be tried. Expired tokens will get refreshed if possible. --- app/jobs/submission_append_job.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/jobs/submission_append_job.rb b/app/jobs/submission_append_job.rb index 0403000..096e5b0 100644 --- a/app/jobs/submission_append_job.rb +++ b/app/jobs/submission_append_job.rb @@ -11,10 +11,6 @@ class SubmissionAppendJob < ApplicationJob def perform(*args) submission_id = args.first submission = Submission.find(submission_id) - if submission.form.active? - submission.append_to_spreadsheet - else - Rails.logger.error("Inactive form: submission_id=#{submission_id} form_id=#{submission.form_id}") - end + submission.append_to_spreadsheet end end From 971eba1355142c26a2ad6317217ac9a4c10e484e Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 18:37:36 +0200 Subject: [PATCH 25/27] Move burger menu file to its own file --- app/javascript/burger_menu.js | 17 ++++++++++++++++ app/javascript/packs/application.js | 30 ++--------------------------- 2 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 app/javascript/burger_menu.js diff --git a/app/javascript/burger_menu.js b/app/javascript/burger_menu.js new file mode 100644 index 0000000..a37539d --- /dev/null +++ b/app/javascript/burger_menu.js @@ -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'); + }); + }); + } +}); diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 472e587..80561d9 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -8,37 +8,11 @@ require("turbolinks").start() require("@rails/activestorage").start() require("channels") +require('burger_menu'); // 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' %>) // or the `imagePath` JavaScript helper below. // // const images = require.context('../images', true) -// const imagePath = (name) => images(name, true) - - -document.addEventListener('DOMContentLoaded', () => { - - // Get all "navbar-burger" elements - const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); - - // 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'); - - }); - }); - } - - }); \ No newline at end of file +// const imagePath = (name) => images(name, true) \ No newline at end of file From 61ee840e1951b3ccc8c0ed72bb19b93e9fd7017b Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 19:14:04 +0200 Subject: [PATCH 26/27] Less specific classes is better we can set CSS properties on the actual HTML elements, here the background color of the html/body --- app/assets/stylesheets/application.css.scss | 2 ++ app/views/layouts/application.html.erb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 3f248e1..77391cb 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -28,6 +28,8 @@ $family-secondary: 'Comfortaa', cursive; // $green: #007932; // hsl(141, 53%, 53%); // $footer-background-color: $light; +$text: $grey-dark; +$body-background-color: #FAFCFE; @import 'bulma/bulma'; .is-font-logo { diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index b421579..7c07b6d 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -11,7 +11,7 @@ <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> - +
From 55143721709167be832573e84ff7f09980e2998f Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 19:15:00 +0200 Subject: [PATCH 27/27] Experiment with colors --- app/assets/stylesheets/application.css.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 77391cb..8c523df 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -28,14 +28,18 @@ $family-secondary: 'Comfortaa', cursive; // $green: #007932; // hsl(141, 53%, 53%); // $footer-background-color: $light; +$blue: #4c82fc; +$primary: $blue; + $text: $grey-dark; $body-background-color: #FAFCFE; + @import 'bulma/bulma'; .is-font-logo { - font-family: 'Lobster', cursive; + font-family: 'Lobster', cursive; } body { - min-height: 100vh; + min-height: 100vh; } \ No newline at end of file