From 374ecb663a02baedcc65bcf83c132871c66eba5f Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Fri, 14 Jan 2022 17:05:08 +0000 Subject: [PATCH 01/13] Add user file based access --- server.rb | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/server.rb b/server.rb index 303cb10..63f8596 100644 --- a/server.rb +++ b/server.rb @@ -7,22 +7,32 @@ end post "/publish" do + user = request.env['HTTP_X_API_KEY'] request_body = request.body.read request = JSON.parse(request_body) id = request['id'] config = {} config = request['configuration'] if request['configuration'] - p config + filename = id + filename = "#{id}_#{user}" if user - File.write("./forms/#{id}", JSON.dump(config)) + File.write("./forms/#{filename}", JSON.dump(config)) config.to_json end get "/published" do + user = request.env['HTTP_X_API_KEY'] + files = [] - forms = Dir.entries("./forms").select { |filename| File.file?("./forms/#{filename}") } + forms = [] + + if user + forms = Dir.entries("./forms").select { |filename| File.file?("./forms/#{filename}") && filename.include?(user) } + else + forms = Dir.entries("./forms").select { |filename| File.file?("./forms/#{filename}") } + end forms.each do |form| File.open("./forms/#{form}") do |f| files << { @@ -37,15 +47,18 @@ end get "/published/:id" do + user = request.env['HTTP_X_API_KEY'] form_content = {} + filename = params['id'] + filename = "#{params['id']}_#{user}" if user - File.open("./forms/#{params['id']}") do |f| + File.open("./forms/#{filename}") do |f| file_content = f.read form_content = JSON.parse(file_content) end { - id: params['id'], + id: filename, values: form_content }.to_json -end \ No newline at end of file +end From fde1077728032ed6926fa52d631d64dc9973ad03 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Fri, 14 Jan 2022 17:05:17 +0000 Subject: [PATCH 02/13] Add sequel and postgres gem --- Gemfile | 2 ++ Gemfile.lock | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Gemfile b/Gemfile index e5666f8..c65d560 100644 --- a/Gemfile +++ b/Gemfile @@ -7,3 +7,5 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } gem "sinatra" gem "pry" gem "thin" +gem "pg" +gem "sequel" diff --git a/Gemfile.lock b/Gemfile.lock index cb931c0..726b567 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,6 +7,7 @@ GEM method_source (1.0.0) mustermann (1.1.1) ruby2_keywords (~> 0.0.1) + pg (1.2.3) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) @@ -14,6 +15,7 @@ GEM rack-protection (2.1.0) rack ruby2_keywords (0.0.5) + sequel (5.52.0) sinatra (2.1.0) mustermann (~> 1.0) rack (~> 2.2) @@ -29,7 +31,9 @@ PLATFORMS x86_64-linux DEPENDENCIES + pg pry + sequel sinatra thin From a3e3116f93fbfc073e6877499852d35b1fff33a1 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Mon, 17 Jan 2022 17:17:38 +0000 Subject: [PATCH 03/13] Add dotenv --- Gemfile | 1 + Gemfile.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index c65d560..f6419f9 100644 --- a/Gemfile +++ b/Gemfile @@ -9,3 +9,4 @@ gem "pry" gem "thin" gem "pg" gem "sequel" +gem "dotenv" diff --git a/Gemfile.lock b/Gemfile.lock index 726b567..d0e6ba2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ GEM specs: coderay (1.1.3) daemons (1.4.1) + dotenv (2.7.6) eventmachine (1.2.7) method_source (1.0.0) mustermann (1.1.1) @@ -31,6 +32,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + dotenv pg pry sequel From a9a87ac7b0240fb8d540ab3d97610688df034291 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Mon, 17 Jan 2022 17:17:58 +0000 Subject: [PATCH 04/13] Add database class and migrations --- db/database.rb | 40 ++++++++++++++++++++++++++++++ db/migrations/1_add_forms_table.rb | 11 ++++++++ 2 files changed, 51 insertions(+) create mode 100644 db/database.rb create mode 100644 db/migrations/1_add_forms_table.rb diff --git a/db/database.rb b/db/database.rb new file mode 100644 index 0000000..60c1b91 --- /dev/null +++ b/db/database.rb @@ -0,0 +1,40 @@ +require "sequel" + +class Migrator + def initialize + Sequel.extension :migration + end + + def destroy(database) + Sequel::Migrator.run(database, "#{__dir__}/migrations", target: 0) + end + + def migrate(database) + Sequel::Migrator.run(database, "#{__dir__}/migrations") + end + + def migrate_to(database, version) + Sequel::Migrator.run(database, "#{__dir__}/migrations", target: version) + end +end + +class Database + def initialize + @migrator = Migrator.new + end + + def connect + database = Sequel.connect(ENV['DATABASE_URL']) + load_extensions_for(database) + + @migrator.migrate(database) + database + end + + private + + def load_extensions_for(database) + database.extension :pg_json + database.extension :pg_array + end +end diff --git a/db/migrations/1_add_forms_table.rb b/db/migrations/1_add_forms_table.rb new file mode 100644 index 0000000..9c3b649 --- /dev/null +++ b/db/migrations/1_add_forms_table.rb @@ -0,0 +1,11 @@ +Sequel.migration do + change do + create_table :forms do + primary_key :id, type: :Bignum + String :username + String :key + String :display_name + column :form, 'json' + end + end +end From f1ccc2a1f49d33e612740ba3a792ce357b30f703 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Mon, 17 Jan 2022 17:18:14 +0000 Subject: [PATCH 05/13] Store forms in the database rather than files --- .env | 1 + README.md | 4 + config.ru | 5 + .../get-condition-evaluation-context | 678 ++++++++++++++++++ example_forms/report-a-terrorist | 281 ++++++++ example_forms/runner-components-test | 379 ++++++++++ example_forms/test | 509 +++++++++++++ loader.rb | 2 + server.rb | 118 +-- 9 files changed, 1934 insertions(+), 43 deletions(-) create mode 100644 .env create mode 100644 config.ru create mode 100644 example_forms/get-condition-evaluation-context create mode 100644 example_forms/report-a-terrorist create mode 100644 example_forms/runner-components-test create mode 100644 example_forms/test create mode 100644 loader.rb diff --git a/.env b/.env new file mode 100644 index 0000000..af23d07 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=postgres://postgres:postgres@localhost/postgres diff --git a/README.md b/README.md index 3b69d61..ef7a966 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # CIFU Forms API Prototype A prototype for a forms API for the collecting information from users team built with Ruby and Sinatra. + +## Running the database with docker + +`docker run --name db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:13` diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..cbaf76f --- /dev/null +++ b/config.ru @@ -0,0 +1,5 @@ +RACK_ENV = ENV['RACK_ENV'] ||= 'development' unless defined?(RACK_ENV) +require_relative 'loader' +require_relative './server' + +run Server diff --git a/example_forms/get-condition-evaluation-context b/example_forms/get-condition-evaluation-context new file mode 100644 index 0000000..015d4bd --- /dev/null +++ b/example_forms/get-condition-evaluation-context @@ -0,0 +1,678 @@ +{ + "startPage": "/uk-passport", + "pages": [ + { + "path": "/uk-passport", + "components": [ + { + "type": "YesNoField", + "name": "ukPassport", + "title": "Do you have a UK passport?", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "section": "checkBeforeYouStart", + "next": [ + { + "path": "/how-many-people" + }, + { + "path": "/anothertest", + "condition": "WLkGmF" + }, + { + "path": "/testconditions", + "condition": "doesntHaveUKPassport" + } + ], + "title": "Do you have a UK passport?" + }, + { + "path": "/how-many-people", + "section": "applicantDetails", + "components": [ + { + "options": { + "classes": "govuk-input--width-10", + "required": true + }, + "type": "SelectField", + "name": "numberOfApplicants", + "title": "How many applicants are there?", + "list": "numberOfApplicants", + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-one-name" + } + ], + "title": "How many applicants are there?" + }, + { + "path": "/applicant-one-name", + "title": "Applicant 1", + "section": "applicantOneDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": { + + } + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-one-address" + } + ] + }, + { + "path": "/applicant-one-address", + "section": "applicantOneDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-two", + "condition": "moreThanOneApplicant" + }, + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/applicant-two", + "title": "Applicant 2", + "section": "applicantTwoDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": { + + } + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-two-address" + } + ] + }, + { + "path": "/applicant-two-address", + "section": "applicantTwoDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-three", + "condition": "moreThanTwoApplicants" + }, + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/applicant-three", + "title": "Applicant 3", + "section": "applicantThreeDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": { + + } + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-three-address" + } + ] + }, + { + "path": "/applicant-three-address", + "section": "applicantThreeDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-four", + "condition": "moreThanThreeApplicants" + }, + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/applicant-four", + "title": "Applicant 4", + "section": "applicantFourDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": { + + } + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/applicant-four-address" + } + ] + }, + { + "path": "/applicant-four-address", + "section": "applicantFourDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/contact-details", + "section": "applicantDetails", + "components": [ + { + "type": "TelephoneNumberField", + "name": "phoneNumber", + "title": "Phone number", + "hint": "If you haven't got a UK phone number, include country code", + "options": { + "required": true + }, + "schema": { + + } + }, + { + "type": "EmailAddressField", + "name": "emailAddress", + "title": "Your email address", + "options": { + "required": true + }, + "schema": { + + } + } + ], + "next": [ + { + "path": "/testconditions" + } + ], + "title": "Applicant contact details" + }, + { + "path": "/summary", + "controller": "./pages/summary.js", + "title": "Summary", + "components": [ + + ], + "next": [ + + ] + }, + { + "path": "/testconditions", + "title": "TestConditions", + "components": [ + { + "name": "pmmRYP", + "options": { + "condition": "bDDfgf" + }, + "type": "Para", + "content": "There Is Someone Called Applicant", + "schema": { + + } + } + ], + "next": [ + { + "path": "/summary" + } + ] + }, + { + "path": "/anothertest", + "title": "AnotherTest", + "components": [ + { + "name": "FnMsqX", + "options": { + + }, + "type": "Para", + "content": "Another Page" + } + ], + "next": [ + { + "path": "/summary" + } + ] + } + ], + "lists": [ + { + "name": "numberOfApplicants", + "title": "Number of people", + "type": "number", + "items": [ + { + "text": "1", + "value": 1, + "description": "", + "condition": "" + }, + { + "text": "2", + "value": 2, + "description": "", + "condition": "" + }, + { + "text": "3", + "value": 3, + "description": "", + "condition": "" + }, + { + "text": "4", + "value": 4, + "description": "", + "condition": "" + } + ] + } + ], + "sections": [ + { + "name": "checkBeforeYouStart", + "title": "Check before you start" + }, + { + "name": "applicantDetails", + "title": "Applicant details" + }, + { + "name": "applicantOneDetails", + "title": "Applicant 1" + }, + { + "name": "applicantTwoDetails", + "title": "Applicant 2" + }, + { + "name": "applicantThreeDetails", + "title": "Applicant 3" + }, + { + "name": "applicantFourDetails", + "title": "Applicant 4" + } + ], + "phaseBanner": { + + }, + "fees": [ + + ], + "outputs": [ + { + "name": "Ric43H5Ctwl4NBDC9x1_4", + "title": "email", + "type": "email", + "outputConfiguration": { + "emailAddress": "jennifermyanh.duong@digital.homeoffice.gov.uk" + } + } + ], + "declaration": "

All the answers you have provided are true to the best of your knowledge.

", + "version": 2, + "conditions": [ + { + "name": "hasUKPassport", + "displayName": "hasUKPassport", + "value": "checkBeforeYouStart.ukPassport==true" + }, + { + "name": "moreThanOneApplicant", + "displayName": "moreThanOneApplicant", + "value": "applicantDetails.numberOfApplicants > 1" + }, + { + "name": "moreThanTwoApplicants", + "displayName": "moreThanTwoApplicants", + "value": "applicantDetails.numberOfApplicants > 2" + }, + { + "name": "moreThanThreeApplicants", + "displayName": "moreThanThreeApplicants", + "value": "applicantDetails.numberOfApplicants > 3" + }, + { + "displayName": "Another", + "name": "nAQyYp", + "value": { + "name": "Another", + "conditions": [ + { + "conditionName": "doesntHaveUKPassport", + "conditionDisplayName": "doesntHaveUKPassport" + } + ] + } + }, + { + "displayName": "Another2", + "name": "QFdzTQ", + "value": { + "name": "Another2", + "conditions": [ + { + "field": { + "name": "checkBeforeYouStart.ukPassport", + "type": "YesNoField", + "display": "Do you have a UK passport?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Another3", + "name": "WLkGmF", + "value": { + "name": "Another3", + "conditions": [ + { + "field": { + "name": "applicantOneDetails.firstName", + "type": "TextField", + "display": "First name" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "Applicant", + "display": "Applicant" + } + }, + { + "coordinator": "and", + "field": { + "name": "checkBeforeYouStart.ukPassport", + "type": "YesNoField", + "display": "Do you have a UK passport?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "name": "bDDfgf", + "displayName": "testCondition", + "value": { + "name": "testCondition", + "conditions": [ + { + "field": { + "name": "applicantOneDetails.firstName", + "type": "TextField", + "display": "First name" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "Applicant", + "display": "Applicant" + } + } + ] + } + }, + { + "name": "doesntHaveUKPassport", + "displayName": "doesntHaveUKPassport", + "value": "checkBeforeYouStart.ukPassport==false" + } + ], + "skipSummary": false +} diff --git a/example_forms/report-a-terrorist b/example_forms/report-a-terrorist new file mode 100644 index 0000000..d2eabf6 --- /dev/null +++ b/example_forms/report-a-terrorist @@ -0,0 +1,281 @@ +{ + "startPage": "/do-you-have-a-link-to-the-evidence", + "pages": [ + { + "title": "Do you have a link to the evidence?", + "path": "/do-you-have-a-link-to-the-evidence", + "components": [ + { + "name": "UjidZI", + "options": {}, + "type": "Para", + "content": "It’s helpful if you can send us links to the relevant pages, or posts if it was posted on social media.", + "schema": {} + }, + { + "name": "rfUYC", + "options": {}, + "type": "Details", + "title": "Help me find the link", + "content": "If you’re on a website, the link appears in the bar at the top of the page. An example of a link is, www.gov.uk/page/1234/content#.", + "schema": {} + }, + { + "type": "RadiosField", + "title": "Do you have a link to the material?", + "options": { + "hideTitle": true + }, + "name": "doyouhavealink", + "schema": {}, + "list": "HTbt4V" + } + ], + "next": [ + { + "path": "/do-you-have-any-evidence" + }, + { + "path": "/yes-i-have-a-link-to-the-material", + "condition": "b-NGgWvGISkJJLuzsJIjv" + } + ], + "section": "PMXq1s" + }, + { + "path": "/do-you-have-any-evidence", + "title": "Do you have any evidence?", + "components": [ + { + "name": "OQrrkG", + "options": {}, + "type": "Para", + "content": "This could be an image or video, for example. Evidence is helpful should the material be deleted before we can find it.It’s safe to save evidence to your device for the purpose of reporting it to us. We recommend deleting it afterwards.", + "schema": {} + }, + { + "name": "3jdOpV", + "options": {}, + "type": "Details", + "title": "Help me take a screenshot", + "content": "Try this:Press the Shift key (⇧), command (or Cmd), and 3The screenshot will be saved to your DesktopYou can now upload it to the formTry this:Press the Ctrl key and the switch window keyThe screenshot will be saved to your DownloadsYou can now upload it to the formIf that doesn’t work, try pressing Ctrl and F5.When viewing the material:Press the Prt Scr key (or similar) to take a copy of your screenPaste the image into Microsoft Paint or a similar applicationSave the file to your computerUpload the file to the formIf that doesn’t work, you may need to search for how to take screenshots on your particular computer model.", + "schema": {} + }, + { + "name": "LU6RMD", + "options": {}, + "type": "RadiosField", + "title": "Do you have any evidence?", + "schema": {}, + "list": "mdmRq9" + } + ], + "next": [ + { + "path": "/is-there-anything-else-you-can-tell-us" + }, + { + "path": "/yes-i-have-evidence", + "condition": "On5IOaSRDSyLs1G7-Dmdy" + } + ], + "section": "PMXq1s" + }, + { + "title": "summary", + "path": "/summary", + "controller": "./pages/summary.js", + "components": [] + }, + { + "path": "/is-there-anything-else-you-can-tell-us", + "title": "Is there anything else you can tell us?", + "components": [ + { + "name": "HETM3o", + "options": {}, + "type": "Para", + "content": "Details may include:who shared the materialwhen it was shareda description, if you haven’t provided a link or evidence", + "schema": {} + }, + { + "name": "evZ-IJ", + "options": { + "required": false + }, + "type": "MultilineTextField", + "title": "Additional Info", + "schema": {} + } + ], + "next": [ + { + "path": "/summary" + } + ], + "section": "PMXq1s" + }, + { + "path": "/yes-i-have-a-link-to-the-material", + "title": "Yes I have a link to the material", + "components": [ + { + "type": "MultilineTextField", + "title": "Link to the material", + "hint": "Please put in the link to the material here", + "name": "blarGGH", + "options": {}, + "schema": {} + } + ], + "next": [ + { + "path": "/do-you-have-any-evidence" + } + ], + "section": "PMXq1s" + }, + { + "path": "/yes-i-have-evidence", + "title": "Yes I have evidence", + "components": [ + { + "name": "koE_ae", + "options": { + "required": false + }, + "type": "FileUploadField", + "title": "Evidence File Upload", + "hint": "Please upload your evidence here", + "schema": {} + } + ], + "next": [ + { + "path": "/is-there-anything-else-you-can-tell-us" + } + ], + "section": "PMXq1s" + } + ], + "lists": [ + { + "title": "linktomateriallist", + "name": "HTbt4V", + "type": "string", + "items": [ + { + "text": "Yes, I do have a link", + "value": "yes" + }, + { + "text": "No, I don't have a link", + "value": "no" + } + ] + }, + { + "title": "evidencelist", + "name": "mdmRq9", + "type": "string", + "items": [ + { + "text": "Yes, I have evidence", + "value": "yes" + }, + { + "text": "No, I don't have evidence", + "value": "no" + } + ] + } + ], + "sections": [ + { + "name": "PMXq1s", + "title": "Evidence" + } + ], + "phaseBanner": {}, + "metadata": {}, + "fees": [], + "outputs": [ + { + "name": "q7mMOb6Eu6EauibjcTFT3", + "title": "powerapps", + "type": "webhook", + "outputConfiguration": { + "url": "https://prod-182.westeurope.logic.azure.com:443/workflows/cfa66b774dad40459bf36e334d860445/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=4nNQTOYk5DnEByQSKMb5jzu_cXJ0NKJuH4KZGBiqvMk" + } + } + ], + "version": 2, + "conditions": [ + { + "name": "b-NGgWvGISkJJLuzsJIjv", + "displayName": "hasLink", + "value": { + "name": "hasLink", + "conditions": [ + { + "field": { + "name": "PMXq1s.doyouhavealink", + "type": "RadiosField", + "display": "Do you have a link to the material? in PMXq1s" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "yes", + "display": "Yes, I do have a link" + } + } + ] + } + }, + { + "name": "xY51EDbc4lPr6kHZl1umG", + "displayName": "noEvidence", + "value": { + "name": "noEvidence", + "conditions": [ + { + "field": { + "name": "PMXq1s.LU6RMD", + "type": "RadiosField", + "display": "Do you have any evidence? in PMXq1s" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "no", + "display": "No, I don't have evidence" + } + } + ] + } + }, + { + "name": "On5IOaSRDSyLs1G7-Dmdy", + "displayName": "hasEvidence", + "value": { + "name": "hasEvidence", + "conditions": [ + { + "field": { + "name": "PMXq1s.LU6RMD", + "type": "RadiosField", + "display": "Do you have any evidence? in PMXq1s" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "yes", + "display": "Yes, I have evidence" + } + } + ] + } + } + ] +} diff --git a/example_forms/runner-components-test b/example_forms/runner-components-test new file mode 100644 index 0000000..3831649 --- /dev/null +++ b/example_forms/runner-components-test @@ -0,0 +1,379 @@ +{ + "startPage": "/do-you-own-a-vehicle", + "pages": [ + { + "title": "Do you own a vehicle?", + "path": "/do-you-own-a-vehicle", + "components": [ + { + "name": "qqbRw1", + "options": {}, + "type": "YesNoField", + "title": "Do you own a vehicle?", + "values": { + "type": "listRef" + }, + "schema": {} + }, + { + "name": "LdOljB", + "options": {}, + "type": "InsetText", + "content": "It does not matter what you pick as it will redirect you to the same page", + "schema": {} + } + ], + "next": [ + { + "path": "/what-address-is-the-vehicle-registered-to" + } + ] + }, + { + "path": "/what-address-is-the-vehicle-registered-to", + "title": "What address is the vehicle registered to?", + "components": [ + { + "name": "sFR4aX", + "options": {}, + "type": "UkAddressField", + "title": "What address is the vehicle registered to?", + "schema": {} + }, + { + "name": "tVlnZa", + "options": {}, + "type": "DateField", + "title": "What date was the vehicle registered at this address?", + "schema": {} + }, + { + "name": "Z0Guyn", + "options": {}, + "type": "CheckboxesField", + "title": "Which Clean Air Zones are you claiming an exemption for?", + "list": "O2uEB1", + "hint": "Please check all that apply, however you are restricted to a maximum of two choices.", + "schema": {} + }, + { + "name": "M8gMK7", + "options": { + "required": false + }, + "type": "FileUploadField", + "title": "Proof of address", + "hint": "This can be a drivers licence or utility bill dated with the last month", + "schema": {} + } + ], + "next": [ + { + "path": "/details-about-your-vehicle" + } + ] + }, + { + "path": "/clean-air-zone-caz-exemption", + "title": "Clean Air Zone (CAZ) Exemption", + "components": [ + { + "name": "MOB13t", + "options": {}, + "type": "Para", + "content": "How to check if you're exempt from paying a charge and how to create a business account, and what support or exemptions are available. ", + "schema": {} + } + ], + "next": [ + { + "path": "/do-you-own-a-vehicle" + } + ], + "controller": "./pages/start.js" + }, + { + "path": "/details-about-your-vehicle", + "title": "Details about your vehicle", + "components": [ + { + "name": "0ZVmN_", + "options": {}, + "type": "AutocompleteField", + "title": "What is the make of you vehicle?", + "list": "-HMHHj", + "schema": {} + }, + { + "name": "gHSgo2", + "options": {}, + "type": "TextField", + "title": "Vehicle Model", + "hint": "For example A1, 740, Elantra", + "schema": {} + }, + { + "name": "4LZ9to", + "options": {}, + "type": "DatePartsField", + "title": "Date you purchased the vehicle?", + "schema": {} + }, + { + "type": "RadiosField", + "title": "What fuel type does your vehicle use?", + "list": "sm_ssM", + "name": "fsfsdfsdf", + "nameHasError": false, + "options": {}, + "schema": {} + }, + { + "name": "chYCuk", + "options": {}, + "type": "MultilineTextField", + "title": "Has the vehicle been modified in any way?", + "hint": "Failure to declare modifications will result in severe penalties if not disclosed at this point.", + "schema": {} + } + ], + "next": [ + { + "path": "/driver-details" + } + ] + }, + { + "path": "/driver-details", + "title": "Driver details", + "components": [ + { + "name": "wJzPKE", + "options": {}, + "type": "NumberField", + "title": "How many people in your household drive this vehicle?", + "schema": {} + }, + { + "name": "PNIThU", + "options": {}, + "type": "TextField", + "title": "Full name of the main driver", + "hint": "Please exclude your title", + "schema": {} + }, + { + "name": "0zL5bB", + "options": {}, + "type": "TelephoneNumberField", + "title": "Contact number", + "hint": "Landline or mobile", + "schema": {} + } + ], + "next": [ + { + "path": "/final-steps" + } + ] + }, + { + "path": "/final-steps", + "title": "final steps", + "components": [ + { + "name": "fkd8av", + "options": {}, + "type": "List", + "title": "Declaration", + "list": "mJHWaC", + "schema": {} + }, + { + "name": "L_2AYe", + "options": {}, + "type": "EmailAddressField", + "title": "Your email address", + "hint": "We will send confirmation of your application to the provided email address", + "schema": {} + } + ], + "next": [ + { + "path": "/summary" + } + ] + }, + { + "path": "/summary", + "title": "Summary", + "components": [], + "next": [], + "controller": "./pages/summary.js" + } + ], + "lists": [ + { + "title": "Vehicle Type", + "name": "ckrDmV", + "type": "string", + "items": [ + { + "text": "Car", + "value": 1 + }, + { + "text": "4 x 4/SUV", + "value": 2 + }, + { + "text": "Van", + "value": 3 + }, + { + "text": "Motorbike/Moped", + "value": 4 + } + ] + }, + { + "title": "Vehicle Make", + "name": "-HMHHj", + "type": "string", + "items": [ + { + "text": "Alfa Romeo", + "value": 1 + }, + { + "text": "BMW", + "value": 2 + }, + { + "text": "Ford", + "value": 3 + }, + { + "text": "Citroen", + "value": 4 + }, + { + "text": "Nissan", + "value": 5 + }, + { + "text": "Honda", + "value": 6 + }, + { + "text": "Mercedes", + "value": 8 + }, + { + "text": "Audi", + "value": 9 + }, + { + "text": "Toyota", + "value": 10 + }, + { + "text": "Hyundai", + "value": 11 + }, + { + "text": "Kia", + "value": 12 + } + ] + }, + { + "title": "Fuel types", + "name": "sm_ssM", + "type": "string", + "items": [ + { + "text": "Diesel", + "value": 1 + }, + { + "text": "Electric", + "value": 2 + }, + { + "text": "Hydrogen", + "value": 3 + }, + { + "text": "Petrol", + "value": 4 + }, + { + "text": "Hybrid", + "value": 5 + } + ] + }, + { + "title": "CAZ Location", + "name": "O2uEB1", + "type": "string", + "items": [ + { + "text": "Bath", + "value": 1 + }, + { + "text": "Bristol", + "value": 2 + }, + { + "text": "Birmingham", + "value": 3 + }, + { + "text": "Cardiff", + "value": 4 + }, + { + "text": "Liverpool", + "value": 6 + }, + { + "text": "Leeds", + "value": 7 + }, + { + "text": "Manchester", + "value": 8 + } + ] + }, + { + "title": "Declaration", + "name": "mJHWaC", + "type": "string", + "items": [ + { + "text": "You are not a Robot", + "value": 1 + }, + { + "text": "You have not previously claimed an exemption", + "value": 2 + }, + { + "text": "You have not omitted or purposely witheld information that might be detrimental to you claim", + "value": 3 + } + ] + } + ], + "sections": [], + "phaseBanner": {}, + "metadata": {}, + "fees": [], + "outputs": [], + "version": 2, + "conditions": [] +} \ No newline at end of file diff --git a/example_forms/test b/example_forms/test new file mode 100644 index 0000000..59b6701 --- /dev/null +++ b/example_forms/test @@ -0,0 +1,509 @@ +{ + "startPage": "/start", + "pages": [ + { + "title": "Start", + "path": "/start", + "components": [], + "next": [ + { + "path": "/uk-passport" + } + ], + "controller": "./pages/start.js" + }, + { + "path": "/uk-passport", + "components": [ + { + "type": "YesNoField", + "name": "ukPassport", + "title": "Do you have a UK passport?", + "options": { + "required": true + }, + "schema": {} + } + ], + "section": "checkBeforeYouStart", + "next": [ + { + "path": "/how-many-people" + }, + { + "path": "/no-uk-passport", + "condition": "doesntHaveUKPassport" + } + ], + "title": "Do you have a UK passport?" + }, + { + "path": "/no-uk-passport", + "title": "You're not eligible for this service", + "components": [ + { + "type": "Para", + "content": "If you still think you're eligible please contact the Foreign and Commonwealth Office.", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [] + }, + { + "path": "/how-many-people", + "section": "applicantDetails", + "components": [ + { + "options": { + "classes": "govuk-input--width-10", + "required": true + }, + "type": "SelectField", + "name": "numberOfApplicants", + "title": "How many applicants are there?", + "list": "numberOfApplicants" + } + ], + "next": [ + { + "path": "/applicant-one" + } + ], + "title": "How many applicants are there?" + }, + { + "path": "/applicant-one", + "title": "Applicant 1", + "section": "applicantOneDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": {} + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": {} + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": {} + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/applicant-one-address" + } + ] + }, + { + "path": "/applicant-one-address", + "section": "applicantOneDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/applicant-two", + "condition": "moreThanOneApplicant" + }, + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/applicant-two", + "title": "Applicant 2", + "section": "applicantTwoDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": {} + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": {} + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": {} + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/applicant-two-address" + } + ] + }, + { + "path": "/applicant-two-address", + "section": "applicantTwoDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/applicant-three", + "condition": "moreThanTwoApplicants" + }, + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/applicant-three", + "title": "Applicant 3", + "section": "applicantThreeDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": {} + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": {} + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": {} + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/applicant-three-address" + } + ] + }, + { + "path": "/applicant-three-address", + "section": "applicantThreeDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/applicant-four", + "condition": "moreThanThreeApplicants" + }, + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/applicant-four", + "title": "Applicant 4", + "section": "applicantFourDetails", + "components": [ + { + "type": "Para", + "content": "Provide the details as they appear on your passport.", + "options": { + "required": true + }, + "schema": {} + }, + { + "type": "TextField", + "name": "firstName", + "title": "First name", + "options": { + "required": true + }, + "schema": {} + }, + { + "options": { + "required": false, + "optionalText": false + }, + "type": "TextField", + "name": "middleName", + "title": "Middle name", + "hint": "If you have a middle name on your passport you must include it here", + "schema": {} + }, + { + "type": "TextField", + "name": "lastName", + "title": "Surname", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/applicant-four-address" + } + ] + }, + { + "path": "/applicant-four-address", + "section": "applicantFourDetails", + "components": [ + { + "type": "UkAddressField", + "name": "address", + "title": "Address", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/contact-details" + } + ], + "title": "Address" + }, + { + "path": "/contact-details", + "section": "applicantDetails", + "components": [ + { + "type": "TelephoneNumberField", + "name": "phoneNumber", + "title": "Phone number", + "hint": "If you haven't got a UK phone number, include country code", + "options": { + "required": true + }, + "schema": {} + }, + { + "type": "EmailAddressField", + "name": "emailAddress", + "title": "Your email address", + "options": { + "required": true + }, + "schema": {} + } + ], + "next": [ + { + "path": "/summary" + } + ], + "title": "Applicant contact details" + }, + { + "path": "/summary", + "controller": "./pages/summary.js", + "title": "Summary", + "components": [], + "next": [] + } + ], + "lists": [ + { + "name": "numberOfApplicants", + "title": "Number of people", + "type": "number", + "items": [ + { + "text": "1", + "value": 1, + "description": "", + "condition": "" + }, + { + "text": "2", + "value": 2, + "description": "", + "condition": "" + }, + { + "text": "3", + "value": 3, + "description": "", + "condition": "" + }, + { + "text": "4", + "value": 4, + "description": "", + "condition": "" + } + ] + } + ], + "sections": [ + { + "name": "checkBeforeYouStart", + "title": "Check before you start" + }, + { + "name": "applicantDetails", + "title": "Applicant details" + }, + { + "name": "applicantOneDetails", + "title": "Applicant 1" + }, + { + "name": "applicantTwoDetails", + "title": "Applicant 2" + }, + { + "name": "applicantThreeDetails", + "title": "Applicant 3" + }, + { + "name": "applicantFourDetails", + "title": "Applicant 4" + } + ], + "phaseBanner": {}, + "fees": [], + "payApiKey": "", + "outputs": [ + { + "name": "Ric43H5Ctwl4NBDC9x1_4", + "title": "email", + "type": "email", + "outputConfiguration": { + "emailAddress": "jennifermyanh.duong@digital.homeoffice.gov.uk" + } + } + ], + "declaration": "

All the answers you have provided are true to the best of your knowledge.

", + "version": 2, + "conditions": [ + { + "name": "hasUKPassport", + "displayName": "hasUKPassport", + "value": "checkBeforeYouStart.ukPassport==true" + }, + { + "name": "doesntHaveUKPassport", + "displayName": "doesntHaveUKPassport", + "value": "checkBeforeYouStart.ukPassport==false" + }, + { + "name": "moreThanOneApplicant", + "displayName": "moreThanOneApplicant", + "value": "applicantDetails.numberOfApplicants > 1" + }, + { + "name": "moreThanTwoApplicants", + "displayName": "moreThanTwoApplicants", + "value": "applicantDetails.numberOfApplicants > 2" + }, + { + "name": "moreThanThreeApplicants", + "displayName": "moreThanThreeApplicants", + "value": "applicantDetails.numberOfApplicants > 3" + } + ] +} diff --git a/loader.rb b/loader.rb new file mode 100644 index 0000000..0297418 --- /dev/null +++ b/loader.rb @@ -0,0 +1,2 @@ +require 'dotenv/load' + diff --git a/server.rb b/server.rb index 63f8596..7ff9c1f 100644 --- a/server.rb +++ b/server.rb @@ -1,64 +1,96 @@ require "sinatra" require "json" require "pry" +require_relative "./db/database" -before do - content_type :json -end - -post "/publish" do - user = request.env['HTTP_X_API_KEY'] - request_body = request.body.read - request = JSON.parse(request_body) +class Server < Sinatra::Base + before do + content_type :json + @database = Database.new.connect + end - id = request['id'] - config = {} - config = request['configuration'] if request['configuration'] - filename = id - filename = "#{id}_#{user}" if user + after do + @database.disconnect + end - File.write("./forms/#{filename}", JSON.dump(config)) + post "/publish" do + user = request.env['HTTP_X_API_KEY'] - config.to_json -end + request_body = request.body.read + request = JSON.parse(request_body) -get "/published" do - user = request.env['HTTP_X_API_KEY'] + id = request['id'] + config = {} + config = request['configuration'] if request['configuration'] - files = [] - forms = [] + @database[:forms].insert( + username: user, + key: id, + display_name: id, + form: Sequel.pg_json(config) + ) - if user - forms = Dir.entries("./forms").select { |filename| File.file?("./forms/#{filename}") && filename.include?(user) } - else - forms = Dir.entries("./forms").select { |filename| File.file?("./forms/#{filename}") } + config.to_json end - forms.each do |form| - File.open("./forms/#{form}") do |f| - files << { - "Key": form, - "DisplayName": form, + + get "/published" do + user = request.env['HTTP_X_API_KEY'] + forms = [] + + forms_for_user(user).each do |form| + forms << { + "Key": form[:key], + "DisplayName": form[:display_name], "FeedbackForm": false } end + + forms.to_json end - files.to_json -end + get "/published/:id" do + user = request.env['HTTP_X_API_KEY'] + form = @database[:forms].where(username: user, key: params['id']).first -get "/published/:id" do - user = request.env['HTTP_X_API_KEY'] - form_content = {} - filename = params['id'] - filename = "#{params['id']}_#{user}" if user + { + id: form[:key], + values: form[:form] + }.to_json + end - File.open("./forms/#{filename}") do |f| - file_content = f.read - form_content = JSON.parse(file_content) + get "/seed/:user" do + seed_data_for_user(params['user']) + + @database[:forms].where(username: params['user']).select(:key).all.to_json end - { - id: filename, - values: form_content - }.to_json + private + + def forms_for_user(user) + forms = @database[:forms].where(username: user).all + + if forms.empty? + seed_data_for_user(user) + return @database[:forms].where(username: user).all + end + + forms + end + + def seed_data_for_user(user) + forms = Dir.entries("./example_forms").select { |filename| File.file?("./example_forms/#{filename}") } + forms.map do |filename| + File.open("./example_forms/#{filename}") do |f| + file_content = f.read + if @database[:forms].where(username: user).where(key: filename).all.count == 0 + @database[:forms].insert( + username: user, + key: filename, + display_name: filename, + form: file_content + ) + end + end + end + end end From 36e9091f060ef98ee16f7e50a9b030ac6c837a90 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Wed, 19 Jan 2022 12:39:17 +0000 Subject: [PATCH 06/13] Set main deploy to manual only --- .github/workflows/deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 18ed769..ad40002 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,8 +1,7 @@ name: Deploy to GOV.UK PaaS on: - push: - branches: [main] + workflow_dispatch: env: REGISTRY: ghcr.io From b71eefe2df72c3e6121e7e3341e8b8e36aa7e5c6 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Wed, 19 Jan 2022 12:57:44 +0000 Subject: [PATCH 07/13] Add service binding to the API --- manifest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manifest.yml b/manifest.yml index 7903776..fe1a412 100644 --- a/manifest.yml +++ b/manifest.yml @@ -3,3 +3,5 @@ applications: - name: cifu-xgov-persistence-service memory: 256M command: bundle exec rackup + services: + - cifu-xgov-form-api-db From bf748ec83c98e6c55506136055d1038b50d407c5 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Wed, 19 Jan 2022 15:11:00 +0000 Subject: [PATCH 08/13] Specify port on run --- manifest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.yml b/manifest.yml index fe1a412..603b827 100644 --- a/manifest.yml +++ b/manifest.yml @@ -2,6 +2,6 @@ applications: - name: cifu-xgov-persistence-service memory: 256M - command: bundle exec rackup + command: bundle exec rackup -p $PORT services: - cifu-xgov-form-api-db From 193dc3147ddee2ebdcd7f56ab56e7d1affcf410b Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Wed, 19 Jan 2022 16:17:36 +0000 Subject: [PATCH 09/13] Update existing forms on publish --- server.rb | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/server.rb b/server.rb index 7ff9c1f..98fa7d1 100644 --- a/server.rb +++ b/server.rb @@ -23,16 +23,29 @@ class Server < Sinatra::Base config = {} config = request['configuration'] if request['configuration'] - @database[:forms].insert( - username: user, - key: id, - display_name: id, - form: Sequel.pg_json(config) - ) + if form_exists_for_user?(user, id) + @database[:forms].where( + username: user, + key: id + ).update( + form: Sequel.pg_json(config) + ) + else + @database[:forms].insert( + username: user, + key: id, + display_name: id, + form: Sequel.pg_json(config) + ) + end config.to_json end + def form_exists_for_user?(user, key) + !@database[:forms].where(username: user, key: key).all.empty? + end + get "/published" do user = request.env['HTTP_X_API_KEY'] forms = [] From 8c7e3b84b1196e76b857e34942f4623743e92902 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Thu, 20 Jan 2022 14:53:55 +0000 Subject: [PATCH 10/13] Handle non existing forms --- server.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.rb b/server.rb index 98fa7d1..c84d62b 100644 --- a/server.rb +++ b/server.rb @@ -65,6 +65,11 @@ def form_exists_for_user?(user, key) user = request.env['HTTP_X_API_KEY'] form = @database[:forms].where(username: user, key: params['id']).first + if form.nil? + response.status = 404 + return {}.to_json + end + { id: form[:key], values: form[:form] From 8cc368fb5925e693a74883c2f7b4aff8532ad4f6 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Tue, 25 Jan 2022 16:34:44 +0000 Subject: [PATCH 11/13] Handle blank username --- server.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server.rb b/server.rb index c84d62b..887c62d 100644 --- a/server.rb +++ b/server.rb @@ -62,7 +62,9 @@ def form_exists_for_user?(user, key) end get "/published/:id" do - user = request.env['HTTP_X_API_KEY'] + api_key = request.env['HTTP_X_API_KEY'] + user = api_key unless api_key.nil? || api_key.empty? + form = @database[:forms].where(username: user, key: params['id']).first if form.nil? From f4108c93abc0cd1391e98f260d905bc76f92e293 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Wed, 26 Jan 2022 17:40:41 +0000 Subject: [PATCH 12/13] Add login and jwt token based authentication --- Gemfile | 1 + Gemfile.lock | 2 ++ server.rb | 32 +++++++++++++++++++++++++++++--- views/login.erb | 10 ++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 views/login.erb diff --git a/Gemfile b/Gemfile index f6419f9..c2c8f27 100644 --- a/Gemfile +++ b/Gemfile @@ -10,3 +10,4 @@ gem "thin" gem "pg" gem "sequel" gem "dotenv" +gem "jwt" diff --git a/Gemfile.lock b/Gemfile.lock index d0e6ba2..0377d70 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,6 +5,7 @@ GEM daemons (1.4.1) dotenv (2.7.6) eventmachine (1.2.7) + jwt (2.3.0) method_source (1.0.0) mustermann (1.1.1) ruby2_keywords (~> 0.0.1) @@ -33,6 +34,7 @@ PLATFORMS DEPENDENCIES dotenv + jwt pg pry sequel diff --git a/server.rb b/server.rb index 887c62d..b6e146e 100644 --- a/server.rb +++ b/server.rb @@ -1,6 +1,7 @@ require "sinatra" require "json" require "pry" +require 'jwt' require_relative "./db/database" class Server < Sinatra::Base @@ -14,7 +15,7 @@ class Server < Sinatra::Base end post "/publish" do - user = request.env['HTTP_X_API_KEY'] + user = authenticated_user request_body = request.body.read request = JSON.parse(request_body) @@ -47,7 +48,7 @@ def form_exists_for_user?(user, key) end get "/published" do - user = request.env['HTTP_X_API_KEY'] + user = authenticated_user forms = [] forms_for_user(user).each do |form| @@ -62,7 +63,7 @@ def form_exists_for_user?(user, key) end get "/published/:id" do - api_key = request.env['HTTP_X_API_KEY'] + api_key = authenticated_user user = api_key unless api_key.nil? || api_key.empty? form = @database[:forms].where(username: user, key: params['id']).first @@ -84,8 +85,33 @@ def form_exists_for_user?(user, key) @database[:forms].where(username: params['user']).select(:key).all.to_json end + get "/login" do + content_type :html + + erb :login + end + + post "/login" do + content_type :html + + payload = { user: params['name'] } + token = JWT.encode payload, nil, 'none' + + redirect "http://localhost:3000/app/auth?token=#{token}" + end + private + def authenticated_user + token = request.env['HTTP_X_API_KEY'] + begin + decoded_token = JWT.decode token, nil, false + return decoded_token[0]["user"] + rescue + return nil + end + end + def forms_for_user(user) forms = @database[:forms].where(username: user).all diff --git a/views/login.erb b/views/login.erb new file mode 100644 index 0000000..13008fe --- /dev/null +++ b/views/login.erb @@ -0,0 +1,10 @@ + + +
+ + + +
+ + + From 07dedff57aa5519f46b3788f773c0e392077ebb4 Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Thu, 27 Jan 2022 12:02:57 +0000 Subject: [PATCH 13/13] Add designer url as environment variable --- server.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.rb b/server.rb index b6e146e..d662b6b 100644 --- a/server.rb +++ b/server.rb @@ -97,7 +97,7 @@ def form_exists_for_user?(user, key) payload = { user: params['name'] } token = JWT.encode payload, nil, 'none' - redirect "http://localhost:3000/app/auth?token=#{token}" + redirect "#{ENV['DESIGNER_URL']}/app/auth?token=#{token}" end private