diff --git a/packages/datadirect-puppeteer/README.md b/packages/datadirect-puppeteer/README.md index e0a5144..0e3d481 100644 --- a/packages/datadirect-puppeteer/README.md +++ b/packages/datadirect-puppeteer/README.md @@ -3,3 +3,34 @@ ![NPM Version](https://img.shields.io/npm/v/datadirect-puppeteer) An implementation of scraping Blackbaud's front-end APIs using Puppeteer + +## Install + +```sh +npm install datadirect-puppeteer datadirect puppeteer +``` + +Realistically, you'll want access to the `datadirect` types and will need to configure `puppeteer`, so they are peer dependencies. + +## Usage + +```ts +import { api } from 'datadirect-puppeteer'; +import { api as types } from 'datadirect'; +import { Page } from 'puppeteer'; + +let page: Page; +// create an authorized Blackbaud LMS web session (https://example.myschoolapp.com) that page refers to + +// optional to explicitly type `groups`, as the `datadirect-puppeteer` method maps types correctly! +const groups: types.datadirect.groupFinderByYear.Response = await api.datadirect.groupFinderByYear(page, { + schoolYearLabel: '2024 - 2025' +}); +const topics = await api.datadirect.sectiontopicsget(page, { + format: 'json'; + active: true; + future: false; + expired: false; + sharedTopics: true; + }, { Id: 12345678 }); +``` diff --git a/packages/datadirect/README.md b/packages/datadirect/README.md index f74b355..6942e5d 100644 --- a/packages/datadirect/README.md +++ b/packages/datadirect/README.md @@ -3,3 +3,49 @@ ![NPM Version](https://img.shields.io/npm/v/datadirect) Types for working with Blackbaud's front-end APIs + +An idiosyncratic collection of TypeScript types for the front-end APIs of Blackbaud's LMS. This functionally serves as my notes for scripting interactions with the LMS. + +## Install + +```sh +npm install datadirect +``` + +## Usage + +```ts +import { api } from 'datadirect'; + +const payload: api.datadirect.groupFinderByYear.Payload = { + schoolYearLabel: '2024 - 2025' +}; +const { input, init } = api.datadirect.groupFinderByYear.prepare(payload); +const response: api.datadirect.groupFinderByYear.Response = await fetch( + input, + init +); +``` + +Each endpoint has a `Payload` and a `Response` type, as well as a method to `prepare()` the `fetch()` parameters. Given that the endpoint needs to be called with suitable authentication (e.g. with authorized session cookies), these are not immediately actionable. [`datadirect-puppeteer`](../datadirect-puppeteer) tackles one approach to this, by assuming an authenticated browser session in a Puppeteer-controlled browser, through which the fetch requests can be passed. + +In the case of endpoints that include path parameters, some additional preparation is required. + +```ts +const payload: api.datadirect.sectiontopicsget.Payload = { + format: 'json'; + active: true; + future: false; + expired: false; + sharedTopics: true; +} +let {input, init} = api.datadirect.sectiontopicsget.prepare(payload); +input = api.Endpoint.preparePath(input, {Id: 12345678}) +// ... +``` + +## Notes + +- Capitalization is "as-found" in the Blackbaud LMS, without corrections. Where multiple sources differ, I have tended towards what seems to be the most common approach. +- Payloads for 'GET' and 'POST' requests are treated identically (at the moment), with the `prepare()` method determining the proper encoding and REST method to use for each endpoint. Should I encounter a situation where that doesn't work, things will change. +- There are no types for path parameters right now, but aspirationally there will be soon! diff --git a/packages/datadirect/src/api/Endpoint.ts b/packages/datadirect/src/api/Endpoint.ts index b4123ea..6d2c478 100644 --- a/packages/datadirect/src/api/Endpoint.ts +++ b/packages/datadirect/src/api/Endpoint.ts @@ -46,9 +46,13 @@ export function preparePath( `${pathParams[p]}$2` ); } - const match = actual.match(/:([a-z_0-9]+)/); - if (match) { - throw new Error(`No values passed for path parameter ${match[1]}`); + const match = Array.from(actual.matchAll(/:([a-z_0-9]+)/g)).map( + (group) => group[1] + ); + if (match.length) { + throw new Error( + `No values passed for path parameter${match.length > 1 ? 's' : ''} ${match.join(', ')}` + ); } return actual; }