From 374ecb663a02baedcc65bcf83c132871c66eba5f Mon Sep 17 00:00:00 2001 From: Dan Burnley Date: Fri, 14 Jan 2022 17:05:08 +0000 Subject: [PATCH 01/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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 From 722c6d0606c1cccbbaaf5fced0c8f9b0a129043a Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Mon, 7 Feb 2022 16:37:57 +0000 Subject: [PATCH 14/28] Initial rust project --- rust-api/Cargo.lock | 1510 ++++++++++++++++++++++++++++++++++ rust-api/Cargo.toml | 12 + rust-api/Dockerfile | 17 + rust-api/docker-compose.yaml | 12 + rust-api/src/main.rs | 31 + 5 files changed, 1582 insertions(+) create mode 100644 rust-api/Cargo.lock create mode 100644 rust-api/Cargo.toml create mode 100644 rust-api/Dockerfile create mode 100644 rust-api/docker-compose.yaml create mode 100644 rust-api/src/main.rs diff --git a/rust-api/Cargo.lock b/rust-api/Cargo.lock new file mode 100644 index 0000000..85da2df --- /dev/null +++ b/rust-api/Cargo.lock @@ -0,0 +1,1510 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "async-trait" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +dependencies = [ + "aes-gcm", + "base64 0.13.0", + "hkdf", + "hmac", + "percent-encoding", + "rand", + "sha2", + "subtle", + "time 0.3.7", + "version_check", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +dependencies = [ + "block-buffer 0.10.1", + "crypto-common", + "generic-array", + "subtle", +] + +[[package]] +name = "encoding_rs" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "h2" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +dependencies = [ + "bytes 1.1.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "headers" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84c647447a07ca16f5fbd05b633e535cc41a08d2d74ab1e08648df53be9cb89" +dependencies = [ + "base64 0.13.0", + "bitflags", + "bytes 1.1.0", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hkdf" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158bc31e00a68e380286904cc598715f861f2b0ccf7aa6fe20c6d0c49ca5d0f6" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" +dependencies = [ + "digest 0.10.1", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes 1.1.0", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes 1.1.0", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +dependencies = [ + "bytes 1.1.0", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 0.4.8", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" + +[[package]] +name = "lock_api" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "multer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836" +dependencies = [ + "bytes 1.1.0", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin", + "tokio", + "version_check", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "poem" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3644551d7c91b980ed277647f16e7afadf3f9f0e24ef3b9f9ecc8d669246544a" +dependencies = [ + "async-trait", + "bytes 1.1.0", + "chrono", + "cookie", + "futures-util", + "headers", + "http", + "hyper", + "mime", + "multer", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "poem-derive", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tempfile", + "thiserror", + "time 0.3.7", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "poem-derive" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c90bc5b98d8ed3822067d4664a2e9ba7ea3a7f2521bb55d7838009f1360fde62" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "poem-openapi" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "547734c796c971f12e901b444eafe22bd8c8f48292ca88e9b0fd96cef386793e" +dependencies = [ + "base64 0.13.0", + "bytes 1.1.0", + "derive_more", + "futures-util", + "mime", + "num-traits", + "poem", + "poem-openapi-derive", + "regex", + "serde", + "serde_json", + "thiserror", + "tokio", + "typed-headers", + "url", +] + +[[package]] +name = "poem-openapi-derive" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650fbd595b55886d9d203ac6530c079251dd4069b26fc3185d3e400a379720ff" +dependencies = [ + "Inflector", + "darling", + "http", + "indexmap", + "mime", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", + "thiserror", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rust-api" +version = "0.1.0" +dependencies = [ + "poem", + "poem-openapi", + "tokio", + "tracing-subscriber", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +dependencies = [ + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.1", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "itoa 1.0.1", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +dependencies = [ + "bytes 1.1.0", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typed-headers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3179a61e9eccceead5f1574fd173cf2e162ac42638b9bf214c6ad0baf7efa24a" +dependencies = [ + "base64 0.11.0", + "bytes 0.5.6", + "chrono", + "http", + "mime", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/rust-api/Cargo.toml b/rust-api/Cargo.toml new file mode 100644 index 0000000..69558e2 --- /dev/null +++ b/rust-api/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-api" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +poem = "1.2.47" +poem-openapi = { version = "1.2.47", features = ["swagger-ui", "url"] } +tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } +tracing-subscriber = "0.2.24" diff --git a/rust-api/Dockerfile b/rust-api/Dockerfile new file mode 100644 index 0000000..32d32c4 --- /dev/null +++ b/rust-api/Dockerfile @@ -0,0 +1,17 @@ +FROM rust + +# We create a new empty shell project to fake the dependencies so we can cache them +WORKDIR /code +RUN USER=root cargo new --bin rust-api + +COPY ./Cargo.lock ./Cargo.lock +COPY ./Cargo.toml ./Cargo.toml + +RUN cargo build --release +RUN rm src/*.rs + +COPY ./src ./src + +RUN cargo install --path . + +CMD ["rust-api"] diff --git a/rust-api/docker-compose.yaml b/rust-api/docker-compose.yaml new file mode 100644 index 0000000..4fde6b0 --- /dev/null +++ b/rust-api/docker-compose.yaml @@ -0,0 +1,12 @@ +version: "3.9" +services: + db: + image: postgres + environment: + POSTGRES_PASSWORD: example + ports: + - "5432:5432" + volumes: + - rust-api-pgdata:/var/lib/postgresql/data/ +volumes: + rust-api-pgdata: {} diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs new file mode 100644 index 0000000..122ffed --- /dev/null +++ b/rust-api/src/main.rs @@ -0,0 +1,31 @@ +use poem::{listener::TcpListener, Route, Server}; +use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService}; + +struct Api; + +#[OpenApi] +impl Api { + #[oai(path = "/test", method = "get")] + async fn index(&self, name: Query>) -> PlainText { + match name.0 { + Some(name) => PlainText(format!("hello, {}!", name)), + None => PlainText("hello!".to_string()), + } + } +} + +#[tokio::main] +async fn main() -> Result<(), std::io::Error> { + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "poem=debug"); + } + tracing_subscriber::fmt::init(); + + let api_service = OpenApiService::new(Api, "Poem REST Demo", "0.0.1-alpha") + .server("http://localhost:3000/api"); + let ui = api_service.swagger_ui(); + + Server::new(TcpListener::bind("0.0.0.0:3000")) + .run(Route::new().nest("/api", api_service).nest("/", ui)) + .await +} From f5371038fd069c0e4908497075ac5105b1f61cf8 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 8 Feb 2022 12:57:10 +0000 Subject: [PATCH 15/28] Moved to native rust, docker postgres --- rust-api/Cargo.lock | 725 ++++++++++++++++++++++++++++++++++- rust-api/Cargo.toml | 3 + rust-api/docker-compose.yaml | 3 +- rust-api/src/api.rs | 14 + rust-api/src/main.rs | 43 ++- 5 files changed, 765 insertions(+), 23 deletions(-) create mode 100644 rust-api/src/api.rs diff --git a/rust-api/Cargo.lock b/rust-api/Cargo.lock index 85da2df..e70b867 100644 --- a/rust-api/Cargo.lock +++ b/rust-api/Cargo.lock @@ -12,6 +12,21 @@ dependencies = [ "regex", ] +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aead" version = "0.4.3" @@ -47,6 +62,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -65,6 +91,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" + [[package]] name = "async-trait" version = "0.1.52" @@ -76,12 +108,36 @@ dependencies = [ "syn", ] +[[package]] +name = "atoi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "backtrace" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.11.0" @@ -118,6 +174,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "0.5.6" @@ -130,6 +198,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + [[package]] name = "cfg-if" version = "1.0.0" @@ -158,6 +232,33 @@ dependencies = [ "generic-array", ] +[[package]] +name = "color-eyre" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6ec7641ff3474b7593009c809db602c414cd97c7d47a78ed004162b74ff96c" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -173,15 +274,31 @@ dependencies = [ "aes-gcm", "base64 0.13.0", "hkdf", - "hmac", + "hmac 0.12.0", "percent-encoding", "rand", - "sha2", + "sha2 0.10.1", "subtle", "time 0.3.7", "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -191,6 +308,51 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "crypto-common" version = "0.1.1" @@ -200,6 +362,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "ctr" version = "0.8.0" @@ -278,6 +450,38 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "encoding_rs" version = "0.8.30" @@ -287,6 +491,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "eyre" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc225d8f637923fe585089fcf03e705c222131232d2c1fb622e84ecf725d0eb8" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "1.7.0" @@ -302,6 +516,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -319,6 +548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -327,6 +557,17 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-macro" version = "0.3.21" @@ -396,6 +637,12 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + [[package]] name = "h2" version = "0.3.11" @@ -420,6 +667,18 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] [[package]] name = "headers" @@ -446,6 +705,15 @@ dependencies = [ "http", ] +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -455,13 +723,29 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "158bc31e00a68e380286904cc598715f861f2b0ccf7aa6fe20c6d0c49ca5d0f6" dependencies = [ - "hmac", + "hmac 0.12.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", ] [[package]] @@ -548,6 +832,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.8.0" @@ -567,6 +857,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -579,6 +878,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -624,6 +932,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "memchr" version = "2.4.1" @@ -636,6 +955,22 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "mio" version = "0.7.14" @@ -677,6 +1012,35 @@ dependencies = [ "version_check", ] +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -724,6 +1088,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.9.0" @@ -736,6 +1109,45 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owo-colors" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20448fd678ec04e6ea15bbe0476874af65e98a01515d667aa49f1434dc44ebf4" + [[package]] name = "parking_lot" version = "0.11.2" @@ -779,6 +1191,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + [[package]] name = "poem" version = "1.2.53" @@ -962,6 +1380,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + [[package]] name = "regex" version = "1.5.4" @@ -1001,12 +1429,21 @@ dependencies = [ name = "rust-api" version = "0.1.0" dependencies = [ + "anyhow", + "color-eyre", "poem", "poem-openapi", + "sqlx", "tokio", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1022,12 +1459,45 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.5" @@ -1090,6 +1560,19 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.1" @@ -1138,6 +1621,117 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692749de69603d81e016212199d73a2e14ee20e2def7d7914919e8db5d4d48b9" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518be6f6fff5ca76f985d434f9c37f3662af279642acf730388f271dff7b9016" +dependencies = [ + "ahash", + "atoi", + "base64 0.13.0", + "bitflags", + "byteorder", + "bytes 1.1.0", + "crc", + "crossbeam-channel", + "crossbeam-queue", + "crossbeam-utils", + "dirs", + "either", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "hmac 0.11.0", + "indexmap", + "itoa 1.0.1", + "libc", + "log", + "md-5", + "memchr", + "once_cell", + "parking_lot", + "percent-encoding", + "rand", + "serde", + "serde_json", + "sha-1", + "sha2 0.9.9", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e45140529cf1f90a5e1c2e561500ca345821a1c513652c8f486bbf07407cc8" +dependencies = [ + "dotenv", + "either", + "heck", + "once_cell", + "proc-macro2", + "quote", + "sha2 0.9.9", + "sqlx-core", + "sqlx-rt", + "syn", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8061cbaa91ee75041514f67a09398c65a64efed72c90151ecd47593bad53da99" +dependencies = [ + "native-tls", + "once_cell", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1275,6 +1869,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.8" @@ -1348,6 +1952,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber 0.3.8", +] + [[package]] name = "tracing-log" version = "0.1.2" @@ -1391,6 +2005,17 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74786ce43333fcf51efe947aed9718fbe46d5c7328ec3f1029e818083966d9aa" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -1431,12 +2056,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.4.1" @@ -1465,6 +2102,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1487,6 +2130,80 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/rust-api/Cargo.toml b/rust-api/Cargo.toml index 69558e2..63324b5 100644 --- a/rust-api/Cargo.toml +++ b/rust-api/Cargo.toml @@ -10,3 +10,6 @@ poem = "1.2.47" poem-openapi = { version = "1.2.47", features = ["swagger-ui", "url"] } tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } tracing-subscriber = "0.2.24" +sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "postgres"] } +color-eyre = "0.6.0" +anyhow = "1.0.53" diff --git a/rust-api/docker-compose.yaml b/rust-api/docker-compose.yaml index 4fde6b0..0b9ecb8 100644 --- a/rust-api/docker-compose.yaml +++ b/rust-api/docker-compose.yaml @@ -3,7 +3,8 @@ services: db: image: postgres environment: - POSTGRES_PASSWORD: example + POSTGRES_USERNAME: postgres + POSTGRES_PASSWORD: password ports: - "5432:5432" volumes: diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs new file mode 100644 index 0000000..5f1eed7 --- /dev/null +++ b/rust-api/src/api.rs @@ -0,0 +1,14 @@ +use poem_openapi::{param::Query, payload::PlainText, OpenApi}; + +pub struct Api; + +#[OpenApi] +impl Api { + #[oai(path = "/test", method = "get")] + async fn index(&self, name: Query>) -> PlainText { + match name.0 { + Some(name) => PlainText(format!("hello, {}!", name)), + None => PlainText("hello!".to_string()), + } + } +} diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index 122ffed..0b072fa 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -1,31 +1,38 @@ +use color_eyre::Report; +use color_eyre::eyre::Result; + use poem::{listener::TcpListener, Route, Server}; -use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService}; +use poem_openapi::{OpenApiService}; +use sqlx::postgres::PgPoolOptions; -struct Api; +mod api; -#[OpenApi] -impl Api { - #[oai(path = "/test", method = "get")] - async fn index(&self, name: Query>) -> PlainText { - match name.0 { - Some(name) => PlainText(format!("hello, {}!", name)), - None => PlainText("hello!".to_string()), - } +fn setup() -> Result<(), Report> { + if std::env::var("RUST_LIB_BACKTRACE").is_err() { + std::env::set_var("RUST_LIB_BACKTRACE", "0") } -} - -#[tokio::main] -async fn main() -> Result<(), std::io::Error> { + color_eyre::install()?; if std::env::var_os("RUST_LOG").is_none() { std::env::set_var("RUST_LOG", "poem=debug"); - } + }; tracing_subscriber::fmt::init(); - let api_service = OpenApiService::new(Api, "Poem REST Demo", "0.0.1-alpha") - .server("http://localhost:3000/api"); + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + setup()?; + let _pool = PgPoolOptions::new() + .max_connections(5) + .connect("postgres://postgres:password@localhost/postgres").await?; + + let api_service = OpenApiService::new(api::Api, "Forms API Rust Prototype", "0.0.1-alpha") + .server("http://0.0.0.0:3000/api"); let ui = api_service.swagger_ui(); Server::new(TcpListener::bind("0.0.0.0:3000")) .run(Route::new().nest("/api", api_service).nest("/", ui)) - .await + .await?; + Ok(()) } From 9cdd4a2b23b6874ab1eeb1253876c801a438e12f Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 8 Feb 2022 13:46:49 +0000 Subject: [PATCH 16/28] Add readme with building instructions --- rust-api/README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 rust-api/README.md diff --git a/rust-api/README.md b/rust-api/README.md new file mode 100644 index 0000000..de53ce4 --- /dev/null +++ b/rust-api/README.md @@ -0,0 +1,39 @@ +# Rust spike of the forms-api-prototype + +## prerequisites + +Rust and either a local Postgres, or Docker. + +### Rust + +Install the rust toolchain from https://sh.rustup.rs with: + +``` shell +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +### Docker + +If you don't have Docker, install it by following the instructions here: https://docs.docker.com/get-docker/ + +### Postgres + +Though Docker is a cleaner way to run Postgres, any local Postgres db will work for testing. +The installation and configuration of which is left as an exercise for the user. Look in `docker-compose.yaml` for the credentials and db we expect. + +## Running the server + + +Please make sure your postgres db is up before starting the server, to do this with the included docker config is simply: + +``` shell +docker-compose up +``` + +Then when your postgres is up, however you have done that, start the api server: + +```shell +cargo run +``` + + From e103390641456c7d162f775a41764f8d7bc230c3 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Wed, 9 Feb 2022 15:31:15 +0000 Subject: [PATCH 17/28] added pool --- rust-api/src/api.rs | 53 +++++++++++++++++++++++++++++++++++++++----- rust-api/src/main.rs | 6 +++-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index 5f1eed7..0d4807d 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -1,14 +1,55 @@ -use poem_openapi::{param::Query, payload::PlainText, OpenApi}; +use poem_openapi::{ + param::{ + Path + }, + payload::{ + Json, + PlainText + }, + OpenApi, + Object +}; + +/// # API design +/// post "/publish" +/// def form_exists_for_user?(user, key) +/// get "/published" +/// get "/published/:id" +/// get "/seed/:user" +/// get "/login" +/// post "/login" +/// def authenticated_user +/// def forms_for_user(user) +/// def seed_data_for_user(user) pub struct Api; #[OpenApi] impl Api { - #[oai(path = "/test", method = "get")] - async fn index(&self, name: Query>) -> PlainText { - match name.0 { - Some(name) => PlainText(format!("hello, {}!", name)), - None => PlainText("hello!".to_string()), + + #[oai(path = "/publish", method = "post")] + async fn create_user(&self, request: Json) -> Json { + if form_exists_for_user("test user".to_string(), "test key".to_string()) { + //update forms where user and id with config=request.configuration + } else { + //update forms where user and id with config=request.configuration and display_name=id } + Json(request.configuration.to_string()) } + + #[oai(path = "/published/:id", method = "get")] + async fn published(&self, id:Path) -> PlainText { + PlainText(format!("Form id: {}!", id.0)) + } +} + +#[derive(Object)] +struct Request { + id: i64, + configuration: String, +} + + +fn form_exists_for_user(_user: String, _key: String) -> bool { + true } diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index 0b072fa..4c245d4 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -3,6 +3,8 @@ use color_eyre::eyre::Result; use poem::{listener::TcpListener, Route, Server}; use poem_openapi::{OpenApiService}; +use poem::EndpointExt; +//use poem::middleware::Cors; use sqlx::postgres::PgPoolOptions; mod api; @@ -23,7 +25,7 @@ fn setup() -> Result<(), Report> { #[tokio::main] async fn main() -> Result<()> { setup()?; - let _pool = PgPoolOptions::new() + let pool = PgPoolOptions::new() .max_connections(5) .connect("postgres://postgres:password@localhost/postgres").await?; @@ -32,7 +34,7 @@ async fn main() -> Result<()> { let ui = api_service.swagger_ui(); Server::new(TcpListener::bind("0.0.0.0:3000")) - .run(Route::new().nest("/api", api_service).nest("/", ui)) + .run(Route::new().nest("/api", api_service).nest("/", ui).data(pool))//.with(Cors)) .await?; Ok(()) } From e698fd9dc9aff648fcff2010f74f5d8e74870c50 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Wed, 9 Feb 2022 17:05:27 +0000 Subject: [PATCH 18/28] Fixed CORS with the poem middleware --- rust-api/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index 4c245d4..6f8b29f 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -4,7 +4,7 @@ use color_eyre::eyre::Result; use poem::{listener::TcpListener, Route, Server}; use poem_openapi::{OpenApiService}; use poem::EndpointExt; -//use poem::middleware::Cors; +use poem::middleware::Cors; use sqlx::postgres::PgPoolOptions; mod api; @@ -34,7 +34,7 @@ async fn main() -> Result<()> { let ui = api_service.swagger_ui(); Server::new(TcpListener::bind("0.0.0.0:3000")) - .run(Route::new().nest("/api", api_service).nest("/", ui).data(pool))//.with(Cors)) + .run(Route::new().nest("/api", api_service).nest("/", ui).data(pool).with(Cors::new())) .await?; Ok(()) } From 5b0b91ffc2cec5732ade47c476f4221cdb1c0106 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Wed, 9 Feb 2022 17:06:01 +0000 Subject: [PATCH 19/28] tweak api design after discussion with @danielburnley --- rust-api/src/api.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index 0d4807d..befa955 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -15,12 +15,11 @@ use poem_openapi::{ /// def form_exists_for_user?(user, key) /// get "/published" /// get "/published/:id" -/// get "/seed/:user" -/// get "/login" -/// post "/login" /// def authenticated_user /// def forms_for_user(user) +/// /// def seed_data_for_user(user) +/// get "/seed/:user" (optional, designer expects forms to exist) pub struct Api; From 73e6e8914240a089598a67973f1d9614a3185478 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Mon, 14 Feb 2022 17:51:50 +0000 Subject: [PATCH 20/28] set up db --- rust-api/.env | 3 ++- rust-api/Cargo.lock | 7 ++++-- rust-api/Cargo.toml | 3 ++- rust-api/src/api.rs | 58 ++++++++++++++++++++------------------------ rust-api/src/main.rs | 34 +++++++++++--------------- 5 files changed, 49 insertions(+), 56 deletions(-) diff --git a/rust-api/.env b/rust-api/.env index 3ac0bbc..8b9af40 100644 --- a/rust-api/.env +++ b/rust-api/.env @@ -1 +1,2 @@ -DATABASE_URL=postgres://postgres:password@localhost/postgres \ No newline at end of file +DATABASE_URL=postgres://postgres:password@localhost/postgres +RUST_LOG=poem=debug \ No newline at end of file diff --git a/rust-api/Cargo.lock b/rust-api/Cargo.lock index 939c082..9ac9a69 100644 --- a/rust-api/Cargo.lock +++ b/rust-api/Cargo.lock @@ -1434,6 +1434,7 @@ dependencies = [ "dotenv", "poem", "poem-openapi", + "serde_json", "sqlx", "tokio", "tracing-subscriber 0.2.25", @@ -1527,10 +1528,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ + "indexmap", "itoa 1.0.1", "ryu", "serde", @@ -1704,6 +1706,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", + "serde_json", "sha2 0.9.9", "sqlx-core", "sqlx-rt", diff --git a/rust-api/Cargo.toml b/rust-api/Cargo.toml index 153734b..3a43347 100644 --- a/rust-api/Cargo.toml +++ b/rust-api/Cargo.toml @@ -10,7 +10,8 @@ poem = "1.2.47" poem-openapi = { version = "1.2.47", features = ["swagger-ui", "url"] } tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } tracing-subscriber = "0.2.24" -sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "postgres"] } +sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "postgres", "json"] } color-eyre = "0.6.0" anyhow = "1.0.53" dotenv = "0.15.0" +serde_json = "1.0.79" diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index 3761c34..32c9003 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -1,18 +1,11 @@ +use poem::web::Data; use poem_openapi::{ - param::{ - Path - }, - payload::{ - Json, - PlainText - }, - OpenApi, - Object + param::Path, + payload::{Json, PlainText}, + Object, OpenApi, }; -use poem::{ - web::Data -}; -use sqlx::postgres::PgPoolOptions; +use serde_json::Value; +use sqlx::postgres::PgPool; /// # API design /// post "/publish" @@ -27,10 +20,8 @@ use sqlx::postgres::PgPoolOptions; pub struct Api; - #[OpenApi] impl Api { - #[oai(path = "/publish", method = "post")] async fn create_user(&self, request: Json) -> Json { if form_exists_for_user("test user".to_string(), "test key".to_string()) { @@ -42,24 +33,20 @@ impl Api { } #[oai(path = "/published/:id", method = "get")] - async fn published(&self, pool: Data<&PgPoolOptions>, id:Path) -> PlainText { - - let countries = sqlx::query_as!(Country, - " - SELECT country, COUNT(*) as count - FROM users - GROUP BY country - WHERE organization = ? - ", - "test" - ) - .fetch_all(&pool) // -> Vec - .await; + async fn published(&self, pool: Data<&PgPool>, id: Path) -> PlainText { + let forms = sqlx::query_as!(Forms, "SELECT * FROM forms") + .fetch_all(pool.0) + .await + .unwrap(); - PlainText(format!("Form id: {}!", id.0)) + let username = forms + .first() + .expect("No forms in DB") + .username + .as_ref() + .unwrap(); + PlainText(format!("Form id: {}!", username)) } - - } #[derive(Object)] @@ -68,7 +55,14 @@ struct Request { configuration: String, } -struct Country { country: String, count: i64 } +#[derive(Debug, sqlx::Type)] +struct Forms { + id: i64, + username: Option, + key: Option, + display_name: Option, + form: Option, +} fn form_exists_for_user(_user: String, _key: String) -> bool { true diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index 3c4ed0b..f5f13e8 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -1,25 +1,19 @@ extern crate dotenv; -use color_eyre::Report; use color_eyre::eyre::Result; -use poem::{listener::TcpListener, Route, Server}; -use poem_openapi::{OpenApiService}; -use poem::EndpointExt; -use poem::middleware::Cors; -use sqlx::postgres::PgPoolOptions; +use color_eyre::Report; use dotenv::dotenv; +use poem::middleware::Cors; +use poem::EndpointExt; +use poem::{listener::TcpListener, Route, Server}; +use poem_openapi::OpenApiService; +use sqlx::postgres::PgPool; mod api; fn setup() -> Result<(), Report> { dotenv().ok(); - if std::env::var("RUST_LIB_BACKTRACE").is_err() { - std::env::set_var("RUST_LIB_BACKTRACE", "0") - } color_eyre::install()?; - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "poem=debug"); - }; tracing_subscriber::fmt::init(); Ok(()) @@ -28,20 +22,20 @@ fn setup() -> Result<(), Report> { #[tokio::main] async fn main() -> Result<()> { setup()?; - let pool = PgPoolOptions::new() - .max_connections(5) - .connect("postgres://postgres:password@localhost/postgres").await?; + let pool = PgPool::connect("postgres://postgres:password@localhost/postgres").await?; let api_service = OpenApiService::new(api::Api, "Forms API Rust Prototype", "0.0.1-alpha") .server("http://0.0.0.0:3000/api"); let ui = api_service.swagger_ui(); Server::new(TcpListener::bind("0.0.0.0:3000")) - .run(Route::new() - .nest("/api", api_service) - .nest("/", ui) - .data(pool) - .with(Cors::new())) + .run( + Route::new() + .nest("/api", api_service) + .nest("/", ui) + .data(pool) + .with(Cors::new()), + ) .await?; Ok(()) } From 6584fcc51699f38da951e7ebf49f4e49d9734c90 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 15 Feb 2022 16:48:56 +0000 Subject: [PATCH 21/28] Inserting forms function done --- rust-api/src/api.rs | 54 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index 32c9003..9d3cea8 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -4,8 +4,11 @@ use poem_openapi::{ payload::{Json, PlainText}, Object, OpenApi, }; -use serde_json::Value; -use sqlx::postgres::PgPool; +use serde_json::{ + Value, + json +}; +use sqlx::{postgres::PgPool, Error}; /// # API design /// post "/publish" @@ -33,9 +36,9 @@ impl Api { } #[oai(path = "/published/:id", method = "get")] - async fn published(&self, pool: Data<&PgPool>, id: Path) -> PlainText { - let forms = sqlx::query_as!(Forms, "SELECT * FROM forms") - .fetch_all(pool.0) + async fn published(&self, data_pool: Data<&PgPool>, id: Path) -> PlainText { + let forms: Vec
= sqlx::query_as!(Form, "SELECT * FROM forms") + .fetch_all(data_pool.0) .await .unwrap(); @@ -47,6 +50,45 @@ impl Api { .unwrap(); PlainText(format!("Form id: {}!", username)) } + + #[oai(path = "/seed/:user", method = "get")] + async fn seed(&self, data_pool: Data<&PgPool>, user: Path) -> PlainText { + // For each json file in /example_forms, add to db, for current user + // insert into airport values (‘San Francisco’,’SFO’,ARRAY[23.42,-34.42, 23.34]); + + new_form( + data_pool.0, + 1, //TODO: Use the filename for the ID + "test user", + "key", + "display_name", + json!({"one": "two"}) + ).await + .expect("Form not inserted into db"); + + + PlainText(format!("forms created for user: {}", user.0)) + } +} + +async fn new_form( + pool: &PgPool, + id: i64, + username: &str, + key: &str, + display_name: &str, + form: Value, +) -> Result { + sqlx::query_as!( + Form, + "INSERT INTO forms VALUES ($1, $2, $3, $4, $5);", + id, + username, + key, + display_name, + form) + .fetch_one(pool) + .await } #[derive(Object)] @@ -56,7 +98,7 @@ struct Request { } #[derive(Debug, sqlx::Type)] -struct Forms { +struct Form { id: i64, username: Option, key: Option, From 295e5ce70928c4246b5e5bfcb99db0b84fd712fd Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 22 Feb 2022 09:27:31 +0000 Subject: [PATCH 22/28] Seeding working --- .gitignore | 1 + rust-api/.env | 2 +- rust-api/Cargo.lock | 2 + rust-api/Cargo.toml | 27 +- .../get-condition-evaluation-context | 678 ++++++++++++++++++ rust-api/example_forms/report-a-terrorist | 281 ++++++++ rust-api/example_forms/runner-components-test | 379 ++++++++++ rust-api/example_forms/test | 509 +++++++++++++ rust-api/src/api.rs | 67 +- rust-api/src/main.rs | 6 +- 10 files changed, 1927 insertions(+), 25 deletions(-) create mode 100644 rust-api/example_forms/get-condition-evaluation-context create mode 100644 rust-api/example_forms/report-a-terrorist create mode 100644 rust-api/example_forms/runner-components-test create mode 100644 rust-api/example_forms/test diff --git a/.gitignore b/.gitignore index 01d802c..33534c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ forms/ +*target/ diff --git a/rust-api/.env b/rust-api/.env index 8b9af40..7ed9819 100644 --- a/rust-api/.env +++ b/rust-api/.env @@ -1,2 +1,2 @@ DATABASE_URL=postgres://postgres:password@localhost/postgres -RUST_LOG=poem=debug \ No newline at end of file +RUST_LOG=debug diff --git a/rust-api/Cargo.lock b/rust-api/Cargo.lock index 9ac9a69..b37986c 100644 --- a/rust-api/Cargo.lock +++ b/rust-api/Cargo.lock @@ -1434,9 +1434,11 @@ dependencies = [ "dotenv", "poem", "poem-openapi", + "rand", "serde_json", "sqlx", "tokio", + "tracing", "tracing-subscriber 0.2.25", ] diff --git a/rust-api/Cargo.toml b/rust-api/Cargo.toml index 3a43347..2ed68b6 100644 --- a/rust-api/Cargo.toml +++ b/rust-api/Cargo.toml @@ -1,17 +1,28 @@ [package] +edition = "2021" name = "rust-api" version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +anyhow = "1.0.53" +color-eyre = "0.6.0" +dotenv = "0.15.0" poem = "1.2.47" -poem-openapi = { version = "1.2.47", features = ["swagger-ui", "url"] } -tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } +rand = "*" +serde_json = "1.0.79" +tracing = "*" tracing-subscriber = "0.2.24" -sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "postgres", "json"] } -color-eyre = "0.6.0" + +[dependencies.poem-openapi] +features = ["swagger-ui", "url"] +version = "1.2.47" + +[dependencies.sqlx] +features = ["runtime-tokio-native-tls", "postgres", "json"] +version = "0.5" + +[dependencies.tokio] anyhow = "1.0.53" dotenv = "0.15.0" +features = ["macros", "rt-multi-thread"] serde_json = "1.0.79" +version = "1.12.0" diff --git a/rust-api/example_forms/get-condition-evaluation-context b/rust-api/example_forms/get-condition-evaluation-context new file mode 100644 index 0000000..015d4bd --- /dev/null +++ b/rust-api/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/rust-api/example_forms/report-a-terrorist b/rust-api/example_forms/report-a-terrorist new file mode 100644 index 0000000..d2eabf6 --- /dev/null +++ b/rust-api/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/rust-api/example_forms/runner-components-test b/rust-api/example_forms/runner-components-test new file mode 100644 index 0000000..3831649 --- /dev/null +++ b/rust-api/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/rust-api/example_forms/test b/rust-api/example_forms/test new file mode 100644 index 0000000..59b6701 --- /dev/null +++ b/rust-api/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/rust-api/src/api.rs b/rust-api/src/api.rs index 9d3cea8..f11e0ca 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -9,6 +9,11 @@ use serde_json::{ json }; use sqlx::{postgres::PgPool, Error}; +use std::{ + fs, + path, +}; +use std::path::PathBuf; /// # API design /// post "/publish" @@ -27,16 +32,19 @@ pub struct Api; impl Api { #[oai(path = "/publish", method = "post")] async fn create_user(&self, request: Json) -> Json { + /* if form_exists_for_user("test user".to_string(), "test key".to_string()) { //update forms where user and id with config=request.configuration } else { //update forms where user and id with config=request.configuration and display_name=id } + / + */ Json(request.configuration.to_string()) } #[oai(path = "/published/:id", method = "get")] - async fn published(&self, data_pool: Data<&PgPool>, id: Path) -> PlainText { + async fn published(&self, data_pool: Data<&PgPool>, _id: Path) -> PlainText { let forms: Vec = sqlx::query_as!(Form, "SELECT * FROM forms") .fetch_all(data_pool.0) .await @@ -53,36 +61,63 @@ impl Api { #[oai(path = "/seed/:user", method = "get")] async fn seed(&self, data_pool: Data<&PgPool>, user: Path) -> PlainText { + //let contents = fs::read_to_string(filename) + // .expect("Something went wrong reading the file"); // For each json file in /example_forms, add to db, for current user // insert into airport values (‘San Francisco’,’SFO’,ARRAY[23.42,-34.42, 23.34]); - new_form( - data_pool.0, - 1, //TODO: Use the filename for the ID - "test user", - "key", - "display_name", - json!({"one": "two"}) - ).await - .expect("Form not inserted into db"); + seed_data_for_user(&user.0, data_pool.0) + .await + .expect("Seeding data failed for user"); PlainText(format!("forms created for user: {}", user.0)) } } +async fn seed_data_for_user(user: &str, pool: &PgPool) -> Result<(), Error> { + let paths = fs::read_dir(&path::Path::new("./example_forms")).unwrap(); + + let file_names = paths.filter_map(|entry| { + entry.ok().and_then(|e| { + let file_name = e.path() + .file_name() + .and_then(|n| { + n.to_str() + .map(String::from) + }); + file_name + }) + }).collect::>(); + + for form_file in file_names { + let path: PathBuf = ["./example_forms", &form_file].iter().collect(); + let file_content = fs::read_to_string(path).unwrap(); + + new_form( + user, + &form_file, + &form_file, + json!(file_content), + pool, + ).await?; + } + Ok(()) +} + async fn new_form( - pool: &PgPool, - id: i64, username: &str, key: &str, display_name: &str, form: Value, -) -> Result { + pool: &PgPool, +) -> Result { sqlx::query_as!( Form, - "INSERT INTO forms VALUES ($1, $2, $3, $4, $5);", - id, + "INSERT INTO forms + VALUES ($1, $2, $3, $4, $5) + RETURNING *;", + rand::random::(), username, key, display_name, @@ -106,6 +141,8 @@ struct Form { form: Option, } +/* fn form_exists_for_user(_user: String, _key: String) -> bool { true } +*/ diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index f5f13e8..1823ba9 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -8,13 +8,16 @@ use poem::EndpointExt; use poem::{listener::TcpListener, Route, Server}; use poem_openapi::OpenApiService; use sqlx::postgres::PgPool; +use tracing_subscriber::EnvFilter; mod api; fn setup() -> Result<(), Report> { dotenv().ok(); color_eyre::install()?; - tracing_subscriber::fmt::init(); + tracing_subscriber::fmt::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); Ok(()) } @@ -22,6 +25,7 @@ fn setup() -> Result<(), Report> { #[tokio::main] async fn main() -> Result<()> { setup()?; + let pool = PgPool::connect("postgres://postgres:password@localhost/postgres").await?; let api_service = OpenApiService::new(api::Api, "Forms API Rust Prototype", "0.0.1-alpha") From b19d0dd15b7bd3f108bf64324187821d4b041849 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 22 Feb 2022 16:54:06 +0000 Subject: [PATCH 23/28] Contract the form response --- rust-api/src/api.rs | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index 9d3cea8..d2db7dd 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -11,15 +11,15 @@ use serde_json::{ use sqlx::{postgres::PgPool, Error}; /// # API design -/// post "/publish" -/// def form_exists_for_user?(user, key) -/// get "/published" -/// get "/published/:id" -/// def authenticated_user -/// def forms_for_user(user) +/// - [ ] post "/publish" +/// - [ ] def form_exists_for_user?(user, key) +/// - [ ] get "/published" +/// - [ ] get "/published/:id" +/// - [ ] def authenticated_user +/// - [ ] def forms_for_user(user) /// -/// def seed_data_for_user(user) -/// get "/seed/:user" (optional, designer expects forms to exist) +/// - [ ] def seed_data_for_user(user) +/// - [ ] get "/seed/:user" (optional, designer expects forms to exist) pub struct Api; @@ -36,19 +36,18 @@ impl Api { } #[oai(path = "/published/:id", method = "get")] - async fn published(&self, data_pool: Data<&PgPool>, id: Path) -> PlainText { - let forms: Vec = sqlx::query_as!(Form, "SELECT * FROM forms") + async fn published(&self, data_pool: Data<&PgPool>, id: Path) -> FormResponse { + let forms: Vec = sqlx::query_as!(Form, "SELECT * FROM forms WHERE id=$1;", id.0) .fetch_all(data_pool.0) .await .unwrap(); - let username = forms - .first() - .expect("No forms in DB") - .username - .as_ref() - .unwrap(); - PlainText(format!("Form id: {}!", username)) + let a_form = forms.first(); + match a_form { + Some(form) => FormResponse::Ok(Json(form.form.as_ref().unwrap().to_string())), + None => FormResponse::NotFound, + } + } #[oai(path = "/seed/:user", method = "get")] @@ -71,6 +70,16 @@ impl Api { } } +#[derive(poem_openapi::ApiResponse)] +enum FormResponse { + /// Return the specified user. + #[oai(status = 200)] + Ok(Json), + /// Return when the specified user is not found. + #[oai(status = 404)] + NotFound +} + async fn new_form( pool: &PgPool, id: i64, From 3da8caca7b8fbfbf5dabe6041c51a0f18506b4de Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Wed, 23 Feb 2022 14:55:47 +0000 Subject: [PATCH 24/28] Publish new forms for user --- rust-api/src/api.rs | 89 ++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index c29b97d..840cf37 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -1,45 +1,49 @@ use poem::web::Data; -use poem_openapi::{ - param::Path, - payload::{Json, PlainText}, - Object, OpenApi, -}; -use serde_json::{ - Value, - json -}; +use poem_openapi::{param::Path, payload::{Json, PlainText}, Object, OpenApi}; +use serde_json::{Value, json}; use sqlx::{postgres::PgPool, Error}; -use std::{ - fs, - path, -}; +use std::{fs, path}; use std::path::PathBuf; /// # API design -/// - [ ] post "/publish" -/// - [ ] def form_exists_for_user?(user, key) +/// - [x] post "/publish" +/// - [x] def form_exists_for_user?(user, key) /// - [ ] get "/published" -/// - [ ] get "/published/:id" +/// - [x] get "/published/:id" /// - [ ] def authenticated_user /// - [ ] def forms_for_user(user) -/// -/// - [ ] def seed_data_for_user(user) -/// - [ ] get "/seed/:user" (optional, designer expects forms to exist) +/// - [x] def seed_data_for_user(user) +/// - [x] get "/seed/:user" (optional, designer expects forms to exist) pub struct Api; #[OpenApi] impl Api { #[oai(path = "/publish", method = "post")] - async fn create_user(&self, request: Json) -> Json { - /* - if form_exists_for_user("test user".to_string(), "test key".to_string()) { - //update forms where user and id with config=request.configuration + async fn publish(&self, data_pool: Data<&PgPool>, request: Json) -> Json { + let user = "test user".to_string(); + let key = "test key".to_string(); + if form_exists_for_user(&user, &key, data_pool.0).await { + sqlx::query!( + "UPDATE forms + SET form = $1 + WHERE username = $2 + AND key = $3;", + json!(request.configuration), + user, + key + ).fetch_one(data_pool.0) + .await.unwrap(); } else { - //update forms where user and id with config=request.configuration and display_name=id + new_form( + &user, + &request.id, + &request.id, + json!(request.configuration), + data_pool.0, + ).await + .expect("new form insert failed"); } - / - */ Json(request.configuration.to_string()) } @@ -51,7 +55,7 @@ impl Api { .unwrap(); let a_form = forms.first(); - match a_form { + return match a_form { Some(form) => FormResponse::Ok(Json(form.form.as_ref().unwrap().to_string())), None => FormResponse::NotFound, } @@ -60,30 +64,26 @@ impl Api { #[oai(path = "/seed/:user", method = "get")] async fn seed(&self, data_pool: Data<&PgPool>, user: Path) -> PlainText { - //let contents = fs::read_to_string(filename) - // .expect("Something went wrong reading the file"); - // For each json file in /example_forms, add to db, for current user - // insert into airport values (‘San Francisco’,’SFO’,ARRAY[23.42,-34.42, 23.34]); - - seed_data_for_user(&user.0, data_pool.0) .await .expect("Seeding data failed for user"); - PlainText(format!("forms created for user: {}", user.0)) + return PlainText(format!("forms created for user: {}", user.0)) } } #[derive(poem_openapi::ApiResponse)] enum FormResponse { - /// Return the specified user. + /// Return the specified form. #[oai(status = 200)] Ok(Json), - /// Return when the specified user is not found. + /// Return when the specified form is not found. #[oai(status = 404)] NotFound } +/// For each json file in /example_forms, add to db, for current user +/// insert into airport values (‘San Francisco’,’SFO’,ARRAY[23.42,-34.42, 23.34]); async fn seed_data_for_user(user: &str, pool: &PgPool) -> Result<(), Error> { let paths = fs::read_dir(&path::Path::new("./example_forms")).unwrap(); @@ -137,7 +137,7 @@ async fn new_form( #[derive(Object)] struct Request { - id: i64, + id: String, configuration: String, } @@ -150,8 +150,15 @@ struct Form { form: Option, } -/* -fn form_exists_for_user(_user: String, _key: String) -> bool { - true + +async fn form_exists_for_user(user: &String, key: &String, pool: &PgPool) -> bool { + + let forms: Vec = sqlx::query_as!(Form, + "SELECT * FROM forms WHERE username=$1 AND key=$2;", + user, key + ).fetch_all(pool) + .await + .unwrap(); + + forms.first().is_some() } -*/ From 607c276097363632ab877f518f05b910182d2bf4 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Mon, 28 Feb 2022 15:16:43 +0000 Subject: [PATCH 25/28] Refactored forms into module get /published for user working Co-authored-by: alice-carr --- rust-api/Makefile | 13 +++++ rust-api/src/api.rs | 108 +++++++++++++++++++----------------------- rust-api/src/forms.rs | 58 +++++++++++++++++++++++ rust-api/src/main.rs | 1 + 4 files changed, 121 insertions(+), 59 deletions(-) create mode 100644 rust-api/Makefile create mode 100644 rust-api/src/forms.rs diff --git a/rust-api/Makefile b/rust-api/Makefile new file mode 100644 index 0000000..505ff23 --- /dev/null +++ b/rust-api/Makefile @@ -0,0 +1,13 @@ +## +# Rust API Spike +## + +run: deps + docker-compose up -d + cargo watch -c -x run + +deps: + @echo "Checking dependencies..." + @which docker-compose || echo "Please install docker-compose" + @which cargo || echo "Please install cargo from rustup.rs" + @cargo watch --version || cargo install cargo-watch -q diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index 840cf37..27b0f7c 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -1,17 +1,20 @@ use poem::web::Data; use poem_openapi::{param::Path, payload::{Json, PlainText}, Object, OpenApi}; -use serde_json::{Value, json}; +use serde_json::{json}; use sqlx::{postgres::PgPool, Error}; use std::{fs, path}; use std::path::PathBuf; +use crate::forms; +use forms::Form; + /// # API design /// - [x] post "/publish" /// - [x] def form_exists_for_user?(user, key) /// - [ ] get "/published" +/// - [ ] def forms_for_user(user) /// - [x] get "/published/:id" /// - [ ] def authenticated_user -/// - [ ] def forms_for_user(user) /// - [x] def seed_data_for_user(user) /// - [x] get "/seed/:user" (optional, designer expects forms to exist) @@ -23,7 +26,7 @@ impl Api { async fn publish(&self, data_pool: Data<&PgPool>, request: Json) -> Json { let user = "test user".to_string(); let key = "test key".to_string(); - if form_exists_for_user(&user, &key, data_pool.0).await { + if Form::form_exists_for_user(&user, &key, data_pool.0).await { sqlx::query!( "UPDATE forms SET form = $1 @@ -35,7 +38,7 @@ impl Api { ).fetch_one(data_pool.0) .await.unwrap(); } else { - new_form( + Form::new_form( &user, &request.id, &request.id, @@ -48,7 +51,7 @@ impl Api { } #[oai(path = "/published/:id", method = "get")] - async fn published(&self, data_pool: Data<&PgPool>, id: Path) -> FormResponse { + async fn published_by_id(&self, data_pool: Data<&PgPool>, id: Path) -> FormResponse { let forms: Vec = sqlx::query_as!(Form, "SELECT * FROM forms WHERE id=$1;", id.0) .fetch_all(data_pool.0) .await @@ -70,6 +73,29 @@ impl Api { return PlainText(format!("forms created for user: {}", user.0)) } + + #[oai(path = "/published", method = "get")] + async fn published(&self, data_pool: Data<&PgPool>) -> Json> { + let user = "tris"; + let forms = Form::forms_for_user(user, data_pool.0).await; + let published_forms = forms + .iter() + .map(|form| { + PublishedForm { + key: form.key.as_ref().unwrap().to_string(), + display_name: form.display_name.as_ref().unwrap().to_string(), + feedback_form: false + }}) + .collect(); + return Json(published_forms) +} + +} +#[derive(Object)] +struct PublishedForm { + key: String, + display_name: String, + feedback_form: bool } #[derive(poem_openapi::ApiResponse)] @@ -82,28 +108,33 @@ enum FormResponse { NotFound } -/// For each json file in /example_forms, add to db, for current user -/// insert into airport values (‘San Francisco’,’SFO’,ARRAY[23.42,-34.42, 23.34]); -async fn seed_data_for_user(user: &str, pool: &PgPool) -> Result<(), Error> { - let paths = fs::read_dir(&path::Path::new("./example_forms")).unwrap(); - let file_names = paths.filter_map(|entry| { - entry.ok().and_then(|e| { - let file_name = e.path() +/// Returns a vec of the filenames inside `folder` +async fn get_files_in_folder(folder: &str) -> Vec { + let paths = fs::read_dir(&path::Path::new(folder)).unwrap(); + + paths.filter_map(|entry_result| { + entry_result.ok().and_then(|entry| { + entry + .path() .file_name() - .and_then(|n| { - n.to_str() + .and_then(|name| { + name.to_str() .map(String::from) - }); - file_name + }) }) - }).collect::>(); + }).collect() +} +/// For each json file in /example_forms, add to db, for current user +async fn seed_data_for_user(user: &str, pool: &PgPool) -> Result<(), Error> { + + let file_names = get_files_in_folder("./example_forms").await; for form_file in file_names { let path: PathBuf = ["./example_forms", &form_file].iter().collect(); let file_content = fs::read_to_string(path).unwrap(); - new_form( + Form::new_form( user, &form_file, &form_file, @@ -114,26 +145,6 @@ async fn seed_data_for_user(user: &str, pool: &PgPool) -> Result<(), Error> { Ok(()) } -async fn new_form( - username: &str, - key: &str, - display_name: &str, - form: Value, - pool: &PgPool, -) -> Result { - sqlx::query_as!( - Form, - "INSERT INTO forms - VALUES ($1, $2, $3, $4, $5) - RETURNING *;", - rand::random::(), - username, - key, - display_name, - form) - .fetch_one(pool) - .await -} #[derive(Object)] struct Request { @@ -141,24 +152,3 @@ struct Request { configuration: String, } -#[derive(Debug, sqlx::Type)] -struct Form { - id: i64, - username: Option, - key: Option, - display_name: Option, - form: Option, -} - - -async fn form_exists_for_user(user: &String, key: &String, pool: &PgPool) -> bool { - - let forms: Vec = sqlx::query_as!(Form, - "SELECT * FROM forms WHERE username=$1 AND key=$2;", - user, key - ).fetch_all(pool) - .await - .unwrap(); - - forms.first().is_some() -} diff --git a/rust-api/src/forms.rs b/rust-api/src/forms.rs new file mode 100644 index 0000000..f74b321 --- /dev/null +++ b/rust-api/src/forms.rs @@ -0,0 +1,58 @@ +use poem_openapi::Object; +use serde_json::{Value}; +use sqlx::{postgres::PgPool, Error}; + +#[derive(Object, Debug, sqlx::Type)] +pub struct Form { + pub id: i64, + pub username: Option, + pub key: Option, + pub display_name: Option, + pub form: Option, +} + +impl Form { + pub async fn new_form( + username: &str, + key: &str, + display_name: &str, + form: Value, + pool: &PgPool, + ) -> Result { + sqlx::query_as!( + Form, + "INSERT INTO forms + VALUES ($1, $2, $3, $4, $5) + RETURNING *;", + rand::random::(), + username, + key, + display_name, + form) + .fetch_one(pool) + .await + } + + pub async fn form_exists_for_user(user: &str, key: &String, pool: &PgPool) -> bool { + sqlx::query_as!( + Form, + "SELECT * FROM forms WHERE username=$1 AND key=$2;", + user, + key + ).fetch_all(pool) + .await + .unwrap() + .first() + .is_some() + } + + pub async fn forms_for_user(user: &str, pool: &PgPool) -> Vec { + sqlx::query_as!( + Form, + "SELECT * FROM forms WHERE username=$1;", + user, + ).fetch_all(pool) + .await + .unwrap() + } +} diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index 1823ba9..ee5bede 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -11,6 +11,7 @@ use sqlx::postgres::PgPool; use tracing_subscriber::EnvFilter; mod api; +mod forms; fn setup() -> Result<(), Report> { dotenv().ok(); From d24d1c443d91be406cd0cc2496b808c6be7c2c2c Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Mon, 28 Feb 2022 17:16:01 +0000 Subject: [PATCH 26/28] API version pulled from cargo.toml Co-authored-by: James Sheppard --- rust-api/Cargo.toml | 1 + rust-api/src/api.rs | 37 ++++++++++++++++++++++++++++--------- rust-api/src/main.rs | 5 ++++- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/rust-api/Cargo.toml b/rust-api/Cargo.toml index 2ed68b6..3d8962f 100644 --- a/rust-api/Cargo.toml +++ b/rust-api/Cargo.toml @@ -2,6 +2,7 @@ edition = "2021" name = "rust-api" version = "0.1.0" + [dependencies] anyhow = "1.0.53" color-eyre = "0.6.0" diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index 27b0f7c..fc16db6 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -8,20 +8,24 @@ use std::path::PathBuf; use crate::forms; use forms::Form; -/// # API design -/// - [x] post "/publish" -/// - [x] def form_exists_for_user?(user, key) -/// - [ ] get "/published" -/// - [ ] def forms_for_user(user) -/// - [x] get "/published/:id" -/// - [ ] def authenticated_user -/// - [x] def seed_data_for_user(user) -/// - [x] get "/seed/:user" (optional, designer expects forms to exist) +/** +# API design +- [x] post "/publish" +- [x] def form_exists_for_user?(user, key) +- [x] get "/published" +- [x] def forms_for_user(user) +- [x] get "/published/:id" +- [ ] def authenticated_user +- [x] def seed_data_for_user(user) +- [x] get "/seed/:user" (optional, designer expects forms to exist) +*/ pub struct Api; #[OpenApi] impl Api { + + /// Publish a form #[oai(path = "/publish", method = "post")] async fn publish(&self, data_pool: Data<&PgPool>, request: Json) -> Json { let user = "test user".to_string(); @@ -50,6 +54,7 @@ impl Api { Json(request.configuration.to_string()) } + /// Get form by its ID #[oai(path = "/published/:id", method = "get")] async fn published_by_id(&self, data_pool: Data<&PgPool>, id: Path) -> FormResponse { let forms: Vec = sqlx::query_as!(Form, "SELECT * FROM forms WHERE id=$1;", id.0) @@ -65,6 +70,7 @@ impl Api { } + /// Create default forms for the user #[oai(path = "/seed/:user", method = "get")] async fn seed(&self, data_pool: Data<&PgPool>, user: Path) -> PlainText { seed_data_for_user(&user.0, data_pool.0) @@ -74,6 +80,7 @@ impl Api { return PlainText(format!("forms created for user: {}", user.0)) } + /// Get all forms for the user #[oai(path = "/published", method = "get")] async fn published(&self, data_pool: Data<&PgPool>) -> Json> { let user = "tris"; @@ -90,6 +97,18 @@ impl Api { return Json(published_forms) } +/* + 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 + */ + } #[derive(Object)] struct PublishedForm { diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index ee5bede..a26cc45 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -29,7 +29,10 @@ async fn main() -> Result<()> { let pool = PgPool::connect("postgres://postgres:password@localhost/postgres").await?; - let api_service = OpenApiService::new(api::Api, "Forms API Rust Prototype", "0.0.1-alpha") + let api_service = OpenApiService::new( + api::Api, + "Forms API Rust Prototype", + env!("CARGO_PKG_VERSION")) .server("http://0.0.0.0:3000/api"); let ui = api_service.swagger_ui(); From 4aa62c4b8f5be43cf851a547df010c32251efa9f Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 1 Mar 2022 17:07:09 +0000 Subject: [PATCH 27/28] Add jwt user auth to /published --- rust-api/Cargo.lock | 47 +++++++++++++++++++++++++----------- rust-api/Cargo.toml | 4 ++++ rust-api/src/api.rs | 57 ++++++++++++++++++++++++++++++++++++++------ rust-api/src/main.rs | 10 ++++++-- 4 files changed, 95 insertions(+), 23 deletions(-) diff --git a/rust-api/Cargo.lock b/rust-api/Cargo.lock index b37986c..6bd7432 100644 --- a/rust-api/Cargo.lock +++ b/rust-api/Cargo.lock @@ -274,10 +274,10 @@ dependencies = [ "aes-gcm", "base64 0.13.0", "hkdf", - "hmac 0.12.0", + "hmac 0.12.1", "percent-encoding", "rand", - "sha2 0.10.1", + "sha2 0.10.2", "subtle", "time 0.3.7", "version_check", @@ -355,11 +355,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array", + "typenum", ] [[package]] @@ -440,13 +441,12 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.1", "crypto-common", - "generic-array", "subtle", ] @@ -735,7 +735,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "158bc31e00a68e380286904cc598715f861f2b0ccf7aa6fe20c6d0c49ca5d0f6" dependencies = [ - "hmac 0.12.0", + "hmac 0.12.1", ] [[package]] @@ -750,11 +750,11 @@ dependencies = [ [[package]] name = "hmac" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", ] [[package]] @@ -887,6 +887,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98328bb4f360e6b2ceb1f95645602c7014000ef0c3809963df8ad3a3a09f8d99" +dependencies = [ + "base64 0.13.0", + "crypto-mac", + "digest 0.9.0", + "hmac 0.11.0", + "serde", + "serde_json", + "sha2 0.9.9", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1432,10 +1447,14 @@ dependencies = [ "anyhow", "color-eyre", "dotenv", + "hmac 0.11.0", + "jwt", "poem", "poem-openapi", "rand", + "serde", "serde_json", + "sha2 0.9.9", "sqlx", "tokio", "tracing", @@ -1580,13 +1599,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.1", + "digest 0.10.3", ] [[package]] diff --git a/rust-api/Cargo.toml b/rust-api/Cargo.toml index 3d8962f..0e3648c 100644 --- a/rust-api/Cargo.toml +++ b/rust-api/Cargo.toml @@ -12,6 +12,10 @@ rand = "*" serde_json = "1.0.79" tracing = "*" tracing-subscriber = "0.2.24" +jwt = "0.15.0" +sha2 = "0.9" +hmac = "0.11" +serde = "1.0.136" [dependencies.poem-openapi] features = ["swagger-ui", "url"] diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index fc16db6..d854fe1 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -1,9 +1,23 @@ -use poem::web::Data; -use poem_openapi::{param::Path, payload::{Json, PlainText}, Object, OpenApi}; +use poem::{ + Request, + web::Data +}; +use poem_openapi::{ + param::Path, + payload::{Json, PlainText}, + Object, + OpenApi, + SecurityScheme, + auth::ApiKey, +}; use serde_json::{json}; use sqlx::{postgres::PgPool, Error}; use std::{fs, path}; use std::path::PathBuf; +use jwt::{SignWithKey, VerifyWithKey}; +use serde::{Deserialize, Serialize}; +use hmac::{Hmac}; +use sha2::Sha256; use crate::forms; use forms::Form; @@ -20,6 +34,8 @@ use forms::Form; - [x] get "/seed/:user" (optional, designer expects forms to exist) */ + +type ServerKey = Hmac; pub struct Api; #[OpenApi] @@ -27,7 +43,7 @@ impl Api { /// Publish a form #[oai(path = "/publish", method = "post")] - async fn publish(&self, data_pool: Data<&PgPool>, request: Json) -> Json { + async fn publish(&self, data_pool: Data<&PgPool>, request: Json) -> Json { let user = "test user".to_string(); let key = "test key".to_string(); if Form::form_exists_for_user(&user, &key, data_pool.0).await { @@ -82,9 +98,9 @@ impl Api { /// Get all forms for the user #[oai(path = "/published", method = "get")] - async fn published(&self, data_pool: Data<&PgPool>) -> Json> { - let user = "tris"; - let forms = Form::forms_for_user(user, data_pool.0).await; + async fn published(&self, auth: MyApiKeyAuthorization, data_pool: Data<&PgPool>) -> Json> { + let user = auth.0.username; + let forms = Form::forms_for_user(&user, data_pool.0).await; let published_forms = forms .iter() .map(|form| { @@ -109,7 +125,34 @@ impl Api { end */ + } + + + +#[derive(Debug, Serialize, Deserialize)] +struct User { + username: String, +} + +/// ApiKey authorization +#[derive(SecurityScheme)] +#[oai( + type = "api_key", + key_name = "X-API-KEY", + in = "header", + checker = "api_checker" +)] +struct MyApiKeyAuthorization(User); + + + +async fn api_checker(req: &Request, api_key: ApiKey) -> Option { + let server_key = req.data::().unwrap(); + VerifyWithKey::::verify_with_key(api_key.key.as_str(), server_key).ok() +} + + #[derive(Object)] struct PublishedForm { key: String, @@ -166,7 +209,7 @@ async fn seed_data_for_user(user: &str, pool: &PgPool) -> Result<(), Error> { #[derive(Object)] -struct Request { +struct OurRequest { id: String, configuration: String, } diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index a26cc45..4d9a1ae 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -9,10 +9,15 @@ use poem::{listener::TcpListener, Route, Server}; use poem_openapi::OpenApiService; use sqlx::postgres::PgPool; use tracing_subscriber::EnvFilter; - +use hmac::{Hmac, NewMac}; +use jwt::{SignWithKey, VerifyWithKey}; +use sha2::Sha256; mod api; mod forms; +const SERVER_KEY: &[u8] = b"123456"; +type ServerKey = Hmac; + fn setup() -> Result<(), Report> { dotenv().ok(); color_eyre::install()?; @@ -35,13 +40,14 @@ async fn main() -> Result<()> { env!("CARGO_PKG_VERSION")) .server("http://0.0.0.0:3000/api"); let ui = api_service.swagger_ui(); - + let server_key = ServerKey::new_from_slice(SERVER_KEY).expect("valid server key"); Server::new(TcpListener::bind("0.0.0.0:3000")) .run( Route::new() .nest("/api", api_service) .nest("/", ui) .data(pool) + .data(server_key) .with(Cors::new()), ) .await?; From 26339ff1b5e616aff68ea57f8f84a8b392d61a77 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Wed, 2 Mar 2022 18:51:24 +0000 Subject: [PATCH 28/28] change plain json responses to typed ones --- rust-api/src/api.rs | 45 +++++++++++++++++++++----------------------- rust-api/src/main.rs | 9 ++++----- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/rust-api/src/api.rs b/rust-api/src/api.rs index d854fe1..09b552e 100644 --- a/rust-api/src/api.rs +++ b/rust-api/src/api.rs @@ -14,7 +14,7 @@ use serde_json::{json}; use sqlx::{postgres::PgPool, Error}; use std::{fs, path}; use std::path::PathBuf; -use jwt::{SignWithKey, VerifyWithKey}; +use jwt::{VerifyWithKey}; use serde::{Deserialize, Serialize}; use hmac::{Hmac}; use sha2::Sha256; @@ -22,19 +22,6 @@ use sha2::Sha256; use crate::forms; use forms::Form; -/** -# API design -- [x] post "/publish" -- [x] def form_exists_for_user?(user, key) -- [x] get "/published" -- [x] def forms_for_user(user) -- [x] get "/published/:id" -- [ ] def authenticated_user -- [x] def seed_data_for_user(user) -- [x] get "/seed/:user" (optional, designer expects forms to exist) -*/ - - type ServerKey = Hmac; pub struct Api; @@ -80,7 +67,14 @@ impl Api { let a_form = forms.first(); return match a_form { - Some(form) => FormResponse::Ok(Json(form.form.as_ref().unwrap().to_string())), + Some(form) => { + let published_form = PublishedForm { + Key: form.key.as_ref().unwrap().to_string(), + DisplayName: form.display_name.as_ref().unwrap().to_string(), + FeedbackForm: false + }; + FormResponse::Ok(Json(published_form)) + } None => FormResponse::NotFound, } @@ -98,16 +92,17 @@ impl Api { /// Get all forms for the user #[oai(path = "/published", method = "get")] - async fn published(&self, auth: MyApiKeyAuthorization, data_pool: Data<&PgPool>) -> Json> { - let user = auth.0.username; + async fn published(&self, data_pool: Data<&PgPool>) -> Json> { + //auth: MyApiKeyAuthorization, + let user = "jwt"; //auth.0.username; let forms = Form::forms_for_user(&user, data_pool.0).await; let published_forms = forms .iter() .map(|form| { PublishedForm { - key: form.key.as_ref().unwrap().to_string(), - display_name: form.display_name.as_ref().unwrap().to_string(), - feedback_form: false + Key: form.key.as_ref().unwrap().to_string(), + DisplayName: form.display_name.as_ref().unwrap().to_string(), + FeedbackForm: false }}) .collect(); return Json(published_forms) @@ -153,18 +148,20 @@ async fn api_checker(req: &Request, api_key: ApiKey) -> Option { } +/// Form objects. The x-gov builder expects pascal case +#[allow(non_snake_case)] #[derive(Object)] struct PublishedForm { - key: String, - display_name: String, - feedback_form: bool + Key: String, + DisplayName: String, + FeedbackForm: bool } #[derive(poem_openapi::ApiResponse)] enum FormResponse { /// Return the specified form. #[oai(status = 200)] - Ok(Json), + Ok(Json), /// Return when the specified form is not found. #[oai(status = 404)] NotFound diff --git a/rust-api/src/main.rs b/rust-api/src/main.rs index 4d9a1ae..24a3b54 100644 --- a/rust-api/src/main.rs +++ b/rust-api/src/main.rs @@ -10,7 +10,6 @@ use poem_openapi::OpenApiService; use sqlx::postgres::PgPool; use tracing_subscriber::EnvFilter; use hmac::{Hmac, NewMac}; -use jwt::{SignWithKey, VerifyWithKey}; use sha2::Sha256; mod api; mod forms; @@ -38,14 +37,14 @@ async fn main() -> Result<()> { api::Api, "Forms API Rust Prototype", env!("CARGO_PKG_VERSION")) - .server("http://0.0.0.0:3000/api"); + .server("http://0.0.0.0:4567/api"); let ui = api_service.swagger_ui(); let server_key = ServerKey::new_from_slice(SERVER_KEY).expect("valid server key"); - Server::new(TcpListener::bind("0.0.0.0:3000")) + Server::new(TcpListener::bind("0.0.0.0:4567")) .run( Route::new() - .nest("/api", api_service) - .nest("/", ui) + .nest("/", api_service) + .nest("/ui", ui) .data(pool) .data(server_key) .with(Cors::new()),