diff --git a/.github/workflows/pull-request-labeler.yml b/.github/workflows/pull-request-labeler.yml index 4735c96..49162a0 100644 --- a/.github/workflows/pull-request-labeler.yml +++ b/.github/workflows/pull-request-labeler.yml @@ -9,4 +9,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/labeler@v4 - if: ${{ github.base_ref == 'main' }} + if: ${{ github.base_ref == 'main' && github.actor != 'dependabot[bot]' }} diff --git a/.gitignore b/.gitignore index 62dd4fc..19fba44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store -.env +.env* # Logs logs diff --git a/README.md b/README.md index 74ed4d8..063b138 100644 --- a/README.md +++ b/README.md @@ -20,30 +20,55 @@ This is a command-line app in Node.js. To run the app: 1. Install NodeJS and the Node Package Manager (npm). 2. Clone this repository. 3. Copy the file `dotenv.sample` and name the new copy `.env`. -4. Edit the `.env` file and fill in your project ID, service account name, and private key. See **Prerequisites** above for how to get this information. Be sure to include the begin/end tags “--BEGIN RSA PRIVATE KEY--“ and “--END RSA PRIVATE KEY--" in your private key, and use line breaks, looking something like this: +4. Edit the `.env` file and fill in your project ID, service account name, and private key. See **Prerequisites** above for how to get this information. See important private key format info below. +5. From the application directory, install the necessary npm libraries: `npm install`. +6. Run the script: `node quickstart.js`. + +### Formatting the Private Key + +NOTE! Be sure to include the begin/end tags `--BEGIN RSA PRIVATE KEY--` and `--END RSA PRIVATE KEY--` in your private key. Also format the line breaks using `\n`. The final string should look something like this: ``` RKS_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n230703de230703de230703de\nb62b0e24b62b0e24b62b0e24\n...\n-----END RSA PRIVATE KEY-----" ``` -5. From the application directory, install the necessary npm libraries: `npm install`. -6. Run the script: `node quickstart.js`. +## Output + +Though primarily intended to show you how to make MyDataHelps API calls, the quickstart also prints out some useful info for debugging. + +### Service Token Output -If successful, the app will print out your token and number of participants in the console, like so: +First, it will print out a service token and participant count: + +The service access token is only valid for a few minutes, but you can copy/paste it into a REST query tool of your choice to try out advanced queries. ``` Obtained service access token: - YOUR TOKEN HERE + SERVICE_TOKEN_HERE Total Participants: 5 ``` -The service access token is only valid for a few minutes, but you can copy/paste it into a REST query tool of your choice to try out advanced queries. +### Participant Token Output + +If you have participants, the quickstart will also print out the first participant's details and a participant token for them. -You can also edit the code to specify a participant identifier and get a participant access token for that participant. This token is only needed for [MyDataHelps Embeddables](https://developer.mydatahelps.org/embeddables), and is useful for testing with the [MyDataHelps Starter Kit](https://github.com/CareEvolution/MyDataHelpsStarterKit). +This token is only needed for [MyDataHelps Embeddables](https://developer.mydatahelps.org/embeddables), and is useful for testing with the [MyDataHelps Starter Kit](https://github.com/CareEvolution/MyDataHelpsStarterKit). + + +``` +Participant Found. ID: 59348b19-6e61-4390-a91f-bd0f90a4f307 +{ + id: '59348b19-6e61-4390-a91f-bd0f90a4f307', + enrolled: true, + ... +} + +Obtained participant access token for 59348b19-6e61-4390-a91f-bd0f90a4f307: PARTICIPANT_TOKEN_HERE +``` ## Troubleshooting If you see an error when running the script, double-check the information in the Prerequisites, particularly the format of the private key. -If you have trouble getting the app to work, feel free to [contact MyDataHelps Support](https://developer.mydatahelps.org/help.html). +If you have trouble getting the app to work, feel free to [contact MyDataHelps Support](https://support.mydatahelps.org/contact-us). diff --git a/dotenv.sample b/dotenv.sample index 5d739e1..681d5be 100644 --- a/dotenv.sample +++ b/dotenv.sample @@ -6,3 +6,5 @@ RKS_SERVICE_ACCOUNT= # You can use \n to separate the lines in your private key RKS_PRIVATE_KEY= + +RKS_ENDPOINT=https://designer.mydatahelps.org \ No newline at end of file diff --git a/quickstart.js b/quickstart.js index 4e0718a..1783f36 100644 --- a/quickstart.js +++ b/quickstart.js @@ -7,18 +7,18 @@ const { v4: uuidv4 } = require('uuid'); // **NOTE!** In a real production app you would want these to be sourced from real environment variables. The .env file is just // a convenience for development. -const rksProjectId = process.env.RKS_PROJECT_ID; -const rksServiceAccount = process.env.RKS_SERVICE_ACCOUNT; +const mdhProjectId = process.env.RKS_PROJECT_ID; +const mdhServiceAccount = process.env.RKS_SERVICE_ACCOUNT; const privateKey = process.env.RKS_PRIVATE_KEY; -const baseUrl = 'https://designer.mydatahelps.org'; +const baseUrl = process.env.RKS_ENDPOINT || "https://designer.mydatahelps.org"; const tokenUrl = `${baseUrl}/identityserver/connect/token`; async function getServiceAccessToken() { const assertion = { - "iss": rksServiceAccount, - "sub": rksServiceAccount, + "iss": mdhServiceAccount, + "sub": mdhServiceAccount, "aud": tokenUrl, "exp": Math.floor(new Date().getTime() / 1000) + 200, "jti": uuidv4() @@ -73,6 +73,29 @@ async function getFromApi(serviceAccessToken, resourceUrl, queryParams = null, r return { data: await response.json(), status: response.status }; } +async function postToApi(serviceAccessToken, resourceUrl, queryParams = null, body = null, raiseError = true) { + + const queryString = queryParams ? `?${new URLSearchParams(queryParams)}` : ''; + const url = `${baseUrl}${resourceUrl}${queryString}`; + + const response = await fetch(url, { + headers: { + "Authorization": `Bearer ${serviceAccessToken}`, + "Accept": "application/json", + "Content-Type": "application/json; charset=utf-8" + }, + method: 'POST', + body: JSON.stringify(body) + }); + + if (!response.ok && raiseError) { + const error = `API call to ${url} failed. ${response.status} - ${await response.text()}`; + throw new Error(error); + } + + return { data: await response.json(), status: response.status }; +} + // Get a participant access token for the specified participant // Used for MyDataHelps Embeddables ONLY async function getParticipantAccessToken(serviceAccessToken, participantID, scopes) { @@ -112,33 +135,40 @@ async function quickstart() { console.log(serviceAccessToken); // Get all participants - url = `/api/v1/administration/projects/${rksProjectId}/participants`; + url = `/api/v1/administration/projects/${mdhProjectId}/participants`; response = await getFromApi(serviceAccessToken, url); const participants = response.data; console.log(`\nTotal Participants: ${participants.totalParticipants}`); - + + // Exit now if no participants found. + if (!participants["participants"]) { + return; + } + + // Get first PPT for debugging + const firstParticipant = participants["participants"][0]; + // Get a specific participant by identifier. We disable 'raiseError' here // so we can handle the 404 case ourselves. - const participantIdentifier = "YOUR_PARTICIPANT_IDENTIFIER" + const participantIdentifier = firstParticipant["participantIdentifier"]; - if (participantIdentifier != "YOUR_PARTICIPANT_IDENTIFIER") { - url = `/api/v1/administration/projects/${rksProjectId}/participants/${participantIdentifier}`; - response = await getFromApi(serviceAccessToken, url, null, false ); - if (response.status === 404) { - console.log("\nParticipant not found."); - } else { - const participant = response.data; - console.log(`\nParticipant Found. ID: ${participant.id}`); - - // NOTE: This piece is only necessary when using MyDataHelps Embeddables in a custom app. - // Most API use cases do NOT require a participant token. - // Be sure to: - // 1. Use the internal ID field (from participant.id above) and NOT participantIdentifier - // 2. Request the correct scope(s) for your needs. - const scopes = "Participant:read SurveyAnswers:read" - const participantAccessToken = await getParticipantAccessToken(serviceAccessToken, participant.id, scopes); - console.log(`\nObtained participant access token for ${participant.id}: ${participantAccessToken}`); - } + url = `/api/v1/administration/projects/${mdhProjectId}/participants/${participantIdentifier}`; + response = await getFromApi(serviceAccessToken, url, null, false ); + if (response.status === 404) { + console.log("\nParticipant not found."); + } else { + const participant = response.data; + console.log(`\nParticipant Found. ID: ${participant.id}`); + console.log(participant); + + // NOTE: This piece is only necessary when using MyDataHelps Embeddables in a custom app. + // Most API use cases do NOT require a participant token. + // Be sure to: + // 1. Use the internal ID field (from participant.id above) and NOT participantIdentifier + // 2. Request the correct scope(s) for your needs. The default ones here mirror the MyDataHelpsStarterKit needs. + const scopes = "Participant:read SurveyTasks:read SurveyAnswers:read Notifications:read DataCollectionSettings:read ExternalAccounts:connect ExternalAccounts:read Project:read"; + const participantAccessToken = await getParticipantAccessToken(serviceAccessToken, participant.id, scopes); + console.log(`\nObtained participant access token for ${participant.id}: ${participantAccessToken}`); } }