From 0dd635b2049b6bc06ad85e425e9a730d3885ebc4 Mon Sep 17 00:00:00 2001 From: Minh Reigen Date: Mon, 2 Aug 2021 13:43:14 -0700 Subject: [PATCH 1/8] reads token and api endpoint urls from a shared source --- package.json | 3 +- src/api_urls.js | 3 ++ .../create-token-with-client-credentials.js | 7 ++-- src/auth/create-token-with-password.js | 3 +- src/auth/expire-token.js | 3 +- src/vendors/set-attribute-value.js | 34 ++++++++++++++----- src/vendors/sync.js | 9 +++-- 7 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 src/api_urls.js diff --git a/package.json b/package.json index e551dcb..776eede 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "get-token-subject": "node ./src/auth/get-token-subject.js", "expire-token": "node ./src/auth/expire-token.js", "sync-vendors": "node ./src/vendors/sync.js", - "set-attribute-value": "node ./src/vendors/set-attribute-value.js" + "set-attribute-value": "node ./src/vendors/set-attribute-value.js", + "get-intake-form": "node ./src/intake-forms/get-intake-form.js" }, "type": "module", "dependencies": { diff --git a/src/api_urls.js b/src/api_urls.js new file mode 100644 index 0000000..c3f43d3 --- /dev/null +++ b/src/api_urls.js @@ -0,0 +1,3 @@ +export const TOKEN_ENDPOINT = "https://api.vendorful.com/auth/v1/token"; +export const VENDORS_API = "https://api.vendorful.com/vendors/v1"; +export const INTAKE_FORMS_API = "https://api.vendorful.com/intake-forms/v1"; diff --git a/src/auth/create-token-with-client-credentials.js b/src/auth/create-token-with-client-credentials.js index 8d1f427..3f29ca6 100644 --- a/src/auth/create-token-with-client-credentials.js +++ b/src/auth/create-token-with-client-credentials.js @@ -1,4 +1,5 @@ import fetch from "node-fetch"; +import { TOKEN_ENDPOINT } from "./../api_urls.js"; /* * Creating a token with grant_type client_credentials can be @@ -15,8 +16,6 @@ import fetch from "node-fetch"; * contacting vendorful support. */ -const TOKEN_ENDPOINT = "https://api.vendorful.com/auth/v1/token"; - async function createToken(id, secret, username) { const params = { grant_type: "client_credentials", @@ -38,7 +37,9 @@ async function main() { const email = process.argv[4]; if (!secret) { console.log("Please pass client_id and client_secret."); - console.log("> yarn run create-token-with-client-credentials "); + console.log( + "> yarn run create-token-with-client-credentials " + ); } else { try { const token = await createToken(id, secret, email); diff --git a/src/auth/create-token-with-password.js b/src/auth/create-token-with-password.js index 0adee6c..9347d14 100644 --- a/src/auth/create-token-with-password.js +++ b/src/auth/create-token-with-password.js @@ -1,5 +1,6 @@ import fetch from "node-fetch"; import readline from "readline"; +import { TOKEN_ENDPOINT } from "./../api_urls.js"; /* * Creating a token with grant_type password is as easy as @@ -7,8 +8,6 @@ import readline from "readline"; * to the token endpoint. */ -const TOKEN_ENDPOINT = "https://api.vendorful.com/auth/v1/token"; - async function createPasswordToken(username, password) { const response = await fetch(TOKEN_ENDPOINT, { method: "POST", diff --git a/src/auth/expire-token.js b/src/auth/expire-token.js index 9e003d9..c6f9984 100644 --- a/src/auth/expire-token.js +++ b/src/auth/expire-token.js @@ -1,4 +1,5 @@ import fetch from "node-fetch"; +import { TOKEN_ENDPOINT } from "./../api_urls.js"; /* * Tokens can be expired early by calling DELETE on the token @@ -6,8 +7,6 @@ import fetch from "node-fetch"; * other authenticated call. */ -const TOKEN_ENDPOINT = "https://api.vendorful.com/auth/v1/token"; - async function expireToken(access_token) { const response = await fetch(TOKEN_ENDPOINT, { method: "DELETE", diff --git a/src/vendors/set-attribute-value.js b/src/vendors/set-attribute-value.js index 4e8fdf1..251c0e2 100644 --- a/src/vendors/set-attribute-value.js +++ b/src/vendors/set-attribute-value.js @@ -1,5 +1,6 @@ import fetch from "node-fetch"; import { readFile } from "fs/promises"; +import { TOKEN_ENDPOINT, VENDORS_API } from "./../api_urls.js"; /* * Attributes can be updated by doing a PUT request to the legal-entities @@ -10,9 +11,6 @@ import { readFile } from "fs/promises"; * that is represented by the token. */ -const TOKEN_ENDPOINT = "https://api.vendorful.com/auth/v1/token"; -const VENDORS_API = "https://api.vendorful.com/vendors/v1"; - // Gets the token by posting secrets.json to the token endpoint. // See secrets-sample.json for an example. async function getToken() { @@ -24,12 +22,22 @@ async function getToken() { return await response.json(); } -async function setAttributeValue({ access_token }, organization_id, entity_id, attribute_id, value) { +async function setAttributeValue( + { access_token }, + organization_id, + entity_id, + attribute_id, + value +) { + console.log("accessToken", accessToken); const url = `${VENDORS_API}/${organization_id}/legal-entities/${entity_id}/attributes/${attribute_id}`; const response = await fetch(url, { method: "put", body: JSON.stringify({ value: value }), - headers: { authorization: `bearer ${access_token}`, "content-type": "application/json" }, + headers: { + authorization: `bearer ${access_token}`, + "content-type": "application/json", + }, }); return await response.json(); } @@ -41,11 +49,21 @@ async function main() { const attribute_id = process.argv[4]; const value = process.argv[5]; if (!value) { - console.log("Please pass organization_id, entity_id, attribute_id, and value."); - console.log("> yarn run set-attribute-value "); + console.log( + "Please pass organization_id, entity_id, attribute_id, and value." + ); + console.log( + "> yarn run set-attribute-value " + ); } else { try { - const result = await setAttributeValue(token, organization_id, entity_id, attribute_id, value); + const result = await setAttributeValue( + token, + organization_id, + entity_id, + attribute_id, + value + ); console.log(JSON.stringify(result, null, 2)); } catch (e) { console.error(e); diff --git a/src/vendors/sync.js b/src/vendors/sync.js index 40e88ef..2a594b1 100644 --- a/src/vendors/sync.js +++ b/src/vendors/sync.js @@ -1,14 +1,11 @@ import fetch from "node-fetch"; import querystring from "querystring"; import { readFile, writeFile } from "fs/promises"; +import { TOKEN_ENDPOINT, VENDORS_API } from "./../api_urls.js"; // Configuration would generally be stored in ENV or config files, but // is inlined here for clarity. -// Update with target environment and api version -const TOKEN_ENDPOINT = "https://api.vendorful.com/auth/v1/token"; -const VENDORS_API = "https://api.vendorful.com/vendors/v1"; - // Update with your organization's Vendorful ID const ORGANIZATION_ID = "acf6ce31-5abb-490e-b76f-15f299ca2853"; @@ -33,7 +30,9 @@ async function getEntities({ access_token }, updated_since) { if (updated_since) params.updated_since = updated_since; // Build url with query params - const url = `${VENDORS_API}/${ORGANIZATION_ID}/legal-entities?${querystring.encode(params)}`; + const url = `${VENDORS_API}/${ORGANIZATION_ID}/legal-entities?${querystring.encode( + params + )}`; // Pass the access_token from the token in the authorization header const response = await fetch(url, { From 26d343146a3bff7c3801aa0358c44c84c8aaf220 Mon Sep 17 00:00:00 2001 From: Minh Reigen Date: Mon, 2 Aug 2021 13:46:17 -0700 Subject: [PATCH 2/8] separate api version for future easy update --- src/api_urls.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api_urls.js b/src/api_urls.js index c3f43d3..897fd86 100644 --- a/src/api_urls.js +++ b/src/api_urls.js @@ -1,3 +1,5 @@ -export const TOKEN_ENDPOINT = "https://api.vendorful.com/auth/v1/token"; -export const VENDORS_API = "https://api.vendorful.com/vendors/v1"; -export const INTAKE_FORMS_API = "https://api.vendorful.com/intake-forms/v1"; +const API_VERSION = "v1"; + +export const TOKEN_ENDPOINT = `https://api.vendorful.com/auth/${API_VERSION}/token`; +export const VENDORS_API = `https://api.vendorful.com/vendors/${API_VERSION}`; +export const INTAKE_FORMS_API = `https://api.vendorful.com/intake-forms/${API_VERSION}`; From 8a50f68c8c06dc54529d80f91c658ac80263c573 Mon Sep 17 00:00:00 2001 From: Minh Reigen Date: Mon, 2 Aug 2021 13:46:47 -0700 Subject: [PATCH 3/8] get intake form api ep --- src/intake-forms/get-intake-form.js | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/intake-forms/get-intake-form.js diff --git a/src/intake-forms/get-intake-form.js b/src/intake-forms/get-intake-form.js new file mode 100644 index 0000000..9f4eee3 --- /dev/null +++ b/src/intake-forms/get-intake-form.js @@ -0,0 +1,47 @@ +import fetch from "node-fetch"; +import { readFile } from "fs/promises"; +import { TOKEN_ENDPOINT, INTAKE_FORMS_API } from "./../api_urls.js"; + +// Gets the token by posting secrets.json to the token endpoint. +// See secrets-sample.json for an example. +async function getToken() { + const response = await fetch(TOKEN_ENDPOINT, { + method: "post", + body: await readFile("secrets.json"), + headers: { "content-type": "application/json" }, + }); + return await response.json(); +} + +async function getIntakeForm({ access_token }, organizationId, formId) { + console.log("access_token", access_token); + const url = `${INTAKE_FORMS_API}/${organizationId}/intake-forms/${formId}`; + const response = await fetch(url, { + method: "get", + headers: { + authorization: `bearer ${access_token}`, + "content-type": "application/json", + }, + }); + + return await response.json(); +} + +async function main() { + const token = await getToken(); + const organizationId = process.argv[2]; + const formId = process.argv[3]; + if (!formId || !organizationId) { + console.log("Please pass organizationId and formId."); + console.log("> yarn run get-intake-form "); + } else { + try { + const result = await getIntakeForm(token, organizationId, formId); + console.log(JSON.stringify(result, null, 2)); + } catch (e) { + console.error(e); + } + } +} + +main(); From 38652a664af5e5f5f1e3695ca2a4ceeb5cc0bc8d Mon Sep 17 00:00:00 2001 From: Minh Reigen Date: Mon, 2 Aug 2021 14:17:05 -0700 Subject: [PATCH 4/8] update create-intake-form-repsonse --- .../create-intake-form-response.js | 75 +++++++++++++++++++ src/intake-forms/get-intake-form.js | 1 - 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/intake-forms/create-intake-form-response.js diff --git a/src/intake-forms/create-intake-form-response.js b/src/intake-forms/create-intake-form-response.js new file mode 100644 index 0000000..b6390dc --- /dev/null +++ b/src/intake-forms/create-intake-form-response.js @@ -0,0 +1,75 @@ +import fetch from "node-fetch"; +import { readFile } from "fs/promises"; +import { TOKEN_ENDPOINT, INTAKE_FORMS_API } from "./../api_urls.js"; + +// Gets the token by posting secrets.json to the token endpoint. +// See secrets-sample.json for an example. +async function getToken() { + const response = await fetch(TOKEN_ENDPOINT, { + method: "post", + body: await readFile("secrets.json"), + headers: { "content-type": "application/json" }, + }); + return await response.json(); +} + +async function createIntakeFormResponse( + { access_token }, + organizationId, + formId, + response_fields +) { + const url = `${INTAKE_FORMS_API}/${organizationId}/intake-forms/${formId}`; + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ response_fields }), + headers: { + authorization: `bearer ${access_token}`, + "content-type": "application/json", + }, + }); + + return await response.json(); +} + +// Id(s) of the field(s) to respond to the intake form with. +// Please see and run get-intake-forms.js to retrieve these field ids +// from the http response's body. +const FIELD_ID_1 = "035cfe94-5a72-45fb-8714-4d87efcd4d19"; +const FIELD_ID_2 = "343upods-5336-4563-sbyy-4664df6724df"; + +async function main() { + const token = await getToken(); + const organizationId = process.argv[2]; + const formId = process.argv[3]; + if (!formId || !organizationId) { + console.log("Please pass organizationId and formId."); + console.log("> yarn run get-intake-form "); + } else { + try { + const responseFields = { + fields: [ + { + id: FIELD_ID_1, + value: "value", + }, + { + id: FIELD_ID_2, + value: ["value_1", "value_2"], + }, + ], + }; + const result = await createIntakeFormResponse( + token, + organizationId, + formId, + responseFields + ); + console.log(JSON.stringify(result, null, 2)); + } catch (e) { + console.error(e); + } + } +} + +main(); diff --git a/src/intake-forms/get-intake-form.js b/src/intake-forms/get-intake-form.js index 9f4eee3..9d5b544 100644 --- a/src/intake-forms/get-intake-form.js +++ b/src/intake-forms/get-intake-form.js @@ -14,7 +14,6 @@ async function getToken() { } async function getIntakeForm({ access_token }, organizationId, formId) { - console.log("access_token", access_token); const url = `${INTAKE_FORMS_API}/${organizationId}/intake-forms/${formId}`; const response = await fetch(url, { method: "get", From 9582c353aa4fa9123494d1823ad75b96c1174dad Mon Sep 17 00:00:00 2001 From: Minh Reigen Date: Mon, 2 Aug 2021 14:20:45 -0700 Subject: [PATCH 5/8] add script to run create intake form response --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 776eede..3a805e3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "expire-token": "node ./src/auth/expire-token.js", "sync-vendors": "node ./src/vendors/sync.js", "set-attribute-value": "node ./src/vendors/set-attribute-value.js", - "get-intake-form": "node ./src/intake-forms/get-intake-form.js" + "get-intake-form": "node ./src/intake-forms/get-intake-form.js", + "create-intake-form-response": "node ./src/intake-forms/create-intake-form-response.js" }, "type": "module", "dependencies": { From d604412f877c5d9e29e11fad5af865439695b63c Mon Sep 17 00:00:00 2001 From: Minh Reigen Date: Tue, 3 Aug 2021 11:17:56 -0700 Subject: [PATCH 6/8] Update create-intake-form-response.js use `fields` instead of `response_fields` --- .../create-intake-form-response.js | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/intake-forms/create-intake-form-response.js b/src/intake-forms/create-intake-form-response.js index b6390dc..0ba8e35 100644 --- a/src/intake-forms/create-intake-form-response.js +++ b/src/intake-forms/create-intake-form-response.js @@ -17,12 +17,12 @@ async function createIntakeFormResponse( { access_token }, organizationId, formId, - response_fields + fields ) { const url = `${INTAKE_FORMS_API}/${organizationId}/intake-forms/${formId}`; const response = await fetch(url, { method: "post", - body: JSON.stringify({ response_fields }), + body: JSON.stringify({ fields }), headers: { authorization: `bearer ${access_token}`, "content-type": "application/json", @@ -35,8 +35,8 @@ async function createIntakeFormResponse( // Id(s) of the field(s) to respond to the intake form with. // Please see and run get-intake-forms.js to retrieve these field ids // from the http response's body. -const FIELD_ID_1 = "035cfe94-5a72-45fb-8714-4d87efcd4d19"; -const FIELD_ID_2 = "343upods-5336-4563-sbyy-4664df6724df"; +const FIELD_ID_1 = "a79a3e19-4a3f-4cff-8ec8-bd2602b93841"; +const FIELD_ID_2 = "9b985c51-7f57-438a-971a-85d92a1f5d21"; async function main() { const token = await getToken(); @@ -47,23 +47,22 @@ async function main() { console.log("> yarn run get-intake-form "); } else { try { - const responseFields = { - fields: [ - { - id: FIELD_ID_1, - value: "value", - }, - { - id: FIELD_ID_2, - value: ["value_1", "value_2"], - }, - ], - }; + const fields = [ + { + id: FIELD_ID_1, + value: "my answer", + }, + { + id: FIELD_ID_2, + value: [0, 1], + }, + ]; + const result = await createIntakeFormResponse( token, organizationId, formId, - responseFields + fields ); console.log(JSON.stringify(result, null, 2)); } catch (e) { From 85068b28d9f4611e10531a1eac9e0a771df1c5f8 Mon Sep 17 00:00:00 2001 From: Summer Smith Date: Thu, 5 Aug 2021 09:50:12 -0400 Subject: [PATCH 7/8] Add documentation for intake forms, example of every field type --- README.md | 26 +++++ src/api_urls.js | 7 +- .../create-intake-form-response.js | 97 ++++++++++++++++--- 3 files changed, 113 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a8bb93f..8c31a00 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,29 @@ Audit history will track the token used to make the change, and will display in - `entity_id` the vendorful id of the legal entity you wish to update - `attribute_id` the vendorful id of the attribute you wish to update - `value` the new value + +## Intake forms + +### /GET + +See it in action: `yarn run get-intake-form organization_id form_id` + +This command assumes you have a `secrets.json` file with appropriate credentials. See [secrets-sample.json](./secrets-sample.json) for the format. + +The `organization_id` is the ID of the organization that created the intake form. +The `form_id` is the ID of the intake form. + +The response returned can be populated with values and used for the /POST with no additional changes required. + +### /POST + +See it in action: `yarn run create-intake-form-response organization_id form_id` + +yarn run create-intake-form-response 96e1349e-3817-41b5-9762-324e2a9f19af 52270eb5-772e-4866-8f34-26b3eeda8dbc + +This command assumes you have a `secrets.json` file with appropriate credentials. See [secrets-sample.json](./secrets-sample.json) for the format. + +The `organization_id` is the ID of the organization that created the intake form. +The `form_id` is the ID of the intake form. + +The field values can be hardcoded in `/src/intake-forms/create-intake-forms`. See that file for an example. diff --git a/src/api_urls.js b/src/api_urls.js index 897fd86..5ecd397 100644 --- a/src/api_urls.js +++ b/src/api_urls.js @@ -1,5 +1,6 @@ const API_VERSION = "v1"; +const BASE_URL = "https://api.vendorful.com"; -export const TOKEN_ENDPOINT = `https://api.vendorful.com/auth/${API_VERSION}/token`; -export const VENDORS_API = `https://api.vendorful.com/vendors/${API_VERSION}`; -export const INTAKE_FORMS_API = `https://api.vendorful.com/intake-forms/${API_VERSION}`; +export const TOKEN_ENDPOINT = `${BASE_URL}/auth/${API_VERSION}/token`; +export const VENDORS_API = `${BASE_URL}/vendors/${API_VERSION}`; +export const INTAKE_FORMS_API = `${BASE_URL}/intake-forms/${API_VERSION}`; diff --git a/src/intake-forms/create-intake-form-response.js b/src/intake-forms/create-intake-form-response.js index 0ba8e35..e056e05 100644 --- a/src/intake-forms/create-intake-form-response.js +++ b/src/intake-forms/create-intake-form-response.js @@ -32,11 +32,91 @@ async function createIntakeFormResponse( return await response.json(); } -// Id(s) of the field(s) to respond to the intake form with. +// Example of fields from /GET response with "value" added // Please see and run get-intake-forms.js to retrieve these field ids // from the http response's body. -const FIELD_ID_1 = "a79a3e19-4a3f-4cff-8ec8-bd2602b93841"; -const FIELD_ID_2 = "9b985c51-7f57-438a-971a-85d92a1f5d21"; +const fields = [ + { + body: null, + id: "874d5c18-c806-4688-babc-28b50b457bda", + multi: null, + name: "Short Text Test", + options: [], + required: false, + type: "string", + value: "test", + }, + { + body: null, + id: "649e1fc1-1950-415d-b97e-a89f3e865969", + multi: null, + name: "Expanded Text Test", + options: [], + required: false, + type: "textarea", + value: "test", + }, + { + body: null, + id: "d5d79f93-9c6f-4960-b5d0-e9bc5fa6a176", + multi: null, + name: "Checkbox UI Test", + options: [], + required: false, + type: "checkbox", + value: true, + }, + { + body: null, + id: "d72fb676-6488-4404-9d89-1cd863031ec5", + multi: null, + name: "Single Select Test: Sandwich Choice", + options: ["Ham", "Roast Beef", "Pimento Cheese"], + required: false, + type: "select", + value: "Roast Beef", + }, + { + body: null, + id: "7d0a3375-3642-44be-9b9b-a9f6b6da26b3", + multi: true, + name: "Multi-Select Test: Cookie Options", + options: ["Snickerdoodle", "Chocolate Chip", "Peanut Butter"], + required: false, + type: "select", + value: ["Snickerdoodle", "Chocolate Chip"], + }, + { + body: null, + id: "4b7ebdff-afb3-484a-96e7-c7adb5ddab8a", + multi: null, + name: "Number Test", + options: [], + required: false, + type: "number", + value: 5, + }, + { + body: null, + id: "7b9be557-5299-447c-8b5f-07214c3ea183", + multi: null, + name: "Range Test", + options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], + required: false, + type: "range", + value: "4", + }, + { + body: null, + id: "f8b048f2-ab5d-4391-84b5-2c7b0a077552", + multi: null, + name: "Date Time Test", + options: [], + required: false, + type: "datetime", + value: new Date(), + }, +]; async function main() { const token = await getToken(); @@ -47,17 +127,6 @@ async function main() { console.log("> yarn run get-intake-form "); } else { try { - const fields = [ - { - id: FIELD_ID_1, - value: "my answer", - }, - { - id: FIELD_ID_2, - value: [0, 1], - }, - ]; - const result = await createIntakeFormResponse( token, organizationId, From c65531398ae18b4e7083cb9dcf9f2db0f1caf6dc Mon Sep 17 00:00:00 2001 From: Summer Smith Date: Thu, 5 Aug 2021 12:31:03 -0400 Subject: [PATCH 8/8] remove console command --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 8c31a00..b929f06 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,6 @@ The response returned can be populated with values and used for the /POST with n See it in action: `yarn run create-intake-form-response organization_id form_id` -yarn run create-intake-form-response 96e1349e-3817-41b5-9762-324e2a9f19af 52270eb5-772e-4866-8f34-26b3eeda8dbc - This command assumes you have a `secrets.json` file with appropriate credentials. See [secrets-sample.json](./secrets-sample.json) for the format. The `organization_id` is the ID of the organization that created the intake form.