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/.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 diff --git a/.gitignore b/.gitignore index 01d802c..33534c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ forms/ +*target/ diff --git a/Gemfile b/Gemfile index e5666f8..c2c8f27 100644 --- a/Gemfile +++ b/Gemfile @@ -7,3 +7,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } gem "sinatra" gem "pry" gem "thin" +gem "pg" +gem "sequel" +gem "dotenv" +gem "jwt" diff --git a/Gemfile.lock b/Gemfile.lock index cb931c0..0377d70 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,10 +3,13 @@ GEM specs: coderay (1.1.3) 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) + pg (1.2.3) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) @@ -14,6 +17,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 +33,11 @@ PLATFORMS x86_64-linux DEPENDENCIES + dotenv + jwt + pg pry + sequel sinatra thin diff --git a/README.md b/README.md index ed74861..fe220ae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # GOV.UK Forms API Prototype A prototype for a forms API for the GOV.UK Forms 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/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 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/manifest.yml b/manifest.yml index 7903776..603b827 100644 --- a/manifest.yml +++ b/manifest.yml @@ -2,4 +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 diff --git a/rust-api/.dockerignore b/rust-api/.dockerignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/rust-api/.dockerignore @@ -0,0 +1 @@ +target/ diff --git a/rust-api/.env b/rust-api/.env new file mode 100644 index 0000000..7ed9819 --- /dev/null +++ b/rust-api/.env @@ -0,0 +1,2 @@ +DATABASE_URL=postgres://postgres:password@localhost/postgres +RUST_LOG=debug diff --git a/rust-api/Cargo.lock b/rust-api/Cargo.lock new file mode 100644 index 0000000..6bd7432 --- /dev/null +++ b/rust-api/Cargo.lock @@ -0,0 +1,2252 @@ +# 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 = "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" +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 = "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" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +dependencies = [ + "proc-macro2", + "quote", + "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" +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 = "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" +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 = "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" +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 = "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" +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 0.12.1", + "percent-encoding", + "rand", + "sha2 0.10.2", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[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" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.1", + "crypto-common", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +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" +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 = "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" +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", + "futures-sink", +] + +[[package]] +name = "futures-core" +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" +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 = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[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" +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" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +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 0.12.1", +] + +[[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]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.3", +] + +[[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 = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[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 = "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" +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 = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = [ + "anyhow", + "color-eyre", + "dotenv", + "hmac 0.11.0", + "jwt", + "poem", + "poem-openapi", + "rand", + "serde", + "serde_json", + "sha2 0.9.9", + "sqlx", + "tokio", + "tracing", + "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" +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 = "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" +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.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "indexmap", + "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.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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[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 = "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", + "serde_json", + "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" +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-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" +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-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" +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 = "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" +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-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" +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 = "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" +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 = "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" +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..0e3648c --- /dev/null +++ b/rust-api/Cargo.toml @@ -0,0 +1,33 @@ +[package] +edition = "2021" +name = "rust-api" +version = "0.1.0" + +[dependencies] +anyhow = "1.0.53" +color-eyre = "0.6.0" +dotenv = "0.15.0" +poem = "1.2.47" +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"] +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/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/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/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 +``` + + diff --git a/rust-api/docker-compose.yaml b/rust-api/docker-compose.yaml new file mode 100644 index 0000000..0b9ecb8 --- /dev/null +++ b/rust-api/docker-compose.yaml @@ -0,0 +1,13 @@ +version: "3.9" +services: + db: + image: postgres + environment: + POSTGRES_USERNAME: postgres + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + volumes: + - rust-api-pgdata:/var/lib/postgresql/data/ +volumes: + rust-api-pgdata: {} 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 new file mode 100644 index 0000000..09b552e --- /dev/null +++ b/rust-api/src/api.rs @@ -0,0 +1,213 @@ +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::{VerifyWithKey}; +use serde::{Deserialize, Serialize}; +use hmac::{Hmac}; +use sha2::Sha256; + +use crate::forms; +use forms::Form; + +type ServerKey = Hmac; +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(); + let key = "test key".to_string(); + if Form::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 { + Form::new_form( + &user, + &request.id, + &request.id, + json!(request.configuration), + data_pool.0, + ).await + .expect("new form insert failed"); + } + 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) + .fetch_all(data_pool.0) + .await + .unwrap(); + + let a_form = forms.first(); + return match a_form { + 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, + } + + } + + /// 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) + .await + .expect("Seeding data failed for user"); + + 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> { + //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(), + DisplayName: form.display_name.as_ref().unwrap().to_string(), + FeedbackForm: false + }}) + .collect(); + 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(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() +} + + +/// Form objects. The x-gov builder expects pascal case +#[allow(non_snake_case)] +#[derive(Object)] +struct PublishedForm { + Key: String, + DisplayName: String, + FeedbackForm: bool +} + +#[derive(poem_openapi::ApiResponse)] +enum FormResponse { + /// Return the specified form. + #[oai(status = 200)] + Ok(Json), + /// Return when the specified form is not found. + #[oai(status = 404)] + NotFound +} + + +/// 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(|name| { + name.to_str() + .map(String::from) + }) + }) + }).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(); + + Form::new_form( + user, + &form_file, + &form_file, + json!(file_content), + pool, + ).await?; + } + Ok(()) +} + + +#[derive(Object)] +struct OurRequest { + id: String, + configuration: String, +} + 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 new file mode 100644 index 0000000..24a3b54 --- /dev/null +++ b/rust-api/src/main.rs @@ -0,0 +1,54 @@ +extern crate dotenv; + +use color_eyre::eyre::Result; +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; +use tracing_subscriber::EnvFilter; +use hmac::{Hmac, NewMac}; +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()?; + tracing_subscriber::fmt::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + Ok(()) +} + +#[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", + env!("CARGO_PKG_VERSION")) + .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:4567")) + .run( + Route::new() + .nest("/", api_service) + .nest("/ui", ui) + .data(pool) + .data(server_key) + .with(Cors::new()), + ) + .await?; + Ok(()) +} diff --git a/server.rb b/server.rb index 303cb10..d662b6b 100644 --- a/server.rb +++ b/server.rb @@ -1,51 +1,142 @@ require "sinatra" require "json" require "pry" +require 'jwt' +require_relative "./db/database" -before do - content_type :json -end +class Server < Sinatra::Base + before do + content_type :json + @database = Database.new.connect + end -post "/publish" do - request_body = request.body.read - request = JSON.parse(request_body) + after do + @database.disconnect + end - id = request['id'] - config = {} - config = request['configuration'] if request['configuration'] - p config + post "/publish" do + user = authenticated_user - File.write("./forms/#{id}", JSON.dump(config)) + request_body = request.body.read + request = JSON.parse(request_body) - config.to_json -end + id = request['id'] + config = {} + config = request['configuration'] if request['configuration'] + + 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 - files = [] - forms = Dir.entries("./forms").select { |filename| File.file?("./forms/#{filename}") } - forms.each do |form| - File.open("./forms/#{form}") do |f| - files << { - "Key": form, - "DisplayName": form, + get "/published" do + user = authenticated_user + 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 + api_key = authenticated_user + user = api_key unless api_key.nil? || api_key.empty? -get "/published/:id" do - form_content = {} + form = @database[:forms].where(username: user, key: params['id']).first + + if form.nil? + response.status = 404 + return {}.to_json + end - File.open("./forms/#{params['id']}") do |f| - file_content = f.read - form_content = JSON.parse(file_content) + { + id: form[:key], + values: form[:form] + }.to_json end - { - id: params['id'], - values: form_content - }.to_json -end \ No newline at end of file + get "/seed/:user" do + seed_data_for_user(params['user']) + + @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 "#{ENV['DESIGNER_URL']}/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 + + 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 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 @@ + + + + + + + + + +