@verii/data-loader 1.0.0-pre.1752076816

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +201 -0
  2. package/jest.config.js +20 -0
  3. package/package.json +45 -0
  4. package/src/batch-issuing/README.md +97 -0
  5. package/src/batch-issuing/constants.js +33 -0
  6. package/src/batch-issuing/fetchers.js +114 -0
  7. package/src/batch-issuing/file-readers.js +15 -0
  8. package/src/batch-issuing/file-writers.js +34 -0
  9. package/src/batch-issuing/index.js +124 -0
  10. package/src/batch-issuing/orchestrators.js +332 -0
  11. package/src/batch-issuing/prepare-data.js +167 -0
  12. package/src/batch-issuing/prompts.js +47 -0
  13. package/src/batch-issuing/validate-directory-exists.js +11 -0
  14. package/src/data-loader.js +14 -0
  15. package/src/helpers/common.js +30 -0
  16. package/src/helpers/compute-activation-date.js +12 -0
  17. package/src/helpers/index.js +8 -0
  18. package/src/helpers/load-csv.js +34 -0
  19. package/src/helpers/load-handlebars-template.js +9 -0
  20. package/src/helpers/parse-column.js +8 -0
  21. package/src/helpers/prepare-variable-sets.js +23 -0
  22. package/src/index.js +3 -0
  23. package/src/vendor-credentials/README.md +72 -0
  24. package/src/vendor-credentials/execute-update.js +32 -0
  25. package/src/vendor-credentials/index.js +49 -0
  26. package/src/vendor-credentials/orchestrator.js +22 -0
  27. package/src/vendor-credentials/prepare-data.js +36 -0
  28. package/test/batch-issuing.test.js +1523 -0
  29. package/test/data/badge-offer.template.json +22 -0
  30. package/test/data/batch-vars-offerids.csv +3 -0
  31. package/test/data/batch-vars.csv +3 -0
  32. package/test/data/batchissuing-variables.csv +3 -0
  33. package/test/data/driver-license-offer.template.json +10 -0
  34. package/test/data/driver-license-variables.csv +3 -0
  35. package/test/data/email-offer.template.json +10 -0
  36. package/test/data/email-variables.csv +3 -0
  37. package/test/data/id-document-offer.template.json +10 -0
  38. package/test/data/id-document-variables.csv +3 -0
  39. package/test/data/national-id-card-offer.template.json +10 -0
  40. package/test/data/national-id-card-variables.csv +3 -0
  41. package/test/data/offer.template.json +22 -0
  42. package/test/data/passport-offer.template.json +10 -0
  43. package/test/data/passport-variables.csv +3 -0
  44. package/test/data/person.template.json +15 -0
  45. package/test/data/phone-offer.template.json +10 -0
  46. package/test/data/phone-variables.csv +2 -0
  47. package/test/data/phones-batch-vars-offerids.csv +3 -0
  48. package/test/data/proof-of-age-offer.template.json +10 -0
  49. package/test/data/proof-of-age-variables.csv +3 -0
  50. package/test/data/resident-permit-offer.template.json +10 -0
  51. package/test/data/resident-permit-variables.csv +3 -0
  52. package/test/data/variables-no-did.csv +3 -0
  53. package/test/data/variables.csv +3 -0
  54. package/test/data/with-bom.csv +3 -0
  55. package/test/vendor-credentials.test.js +227 -0
@@ -0,0 +1,30 @@
1
+ const console = require('console');
2
+ const chalk = require('chalk');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const writeFile = (filePath, fileContent) => {
7
+ const fileBasename = path.basename(filePath, '.*');
8
+
9
+ console.info(`${chalk.green('Writing:')} ${chalk.whiteBright(fileBasename)}`);
10
+
11
+ fs.writeFileSync(filePath, fileContent, 'utf8');
12
+ };
13
+
14
+ const readFile = (filePath, missingError) => {
15
+ if (!fs.existsSync(filePath)) {
16
+ throw new Error(missingError);
17
+ }
18
+
19
+ return fs.readFileSync(filePath, 'utf8');
20
+ };
21
+
22
+ const printError = (ex) => console.error(ex);
23
+ const printInfo = (data) => console.info(data);
24
+
25
+ module.exports = {
26
+ printInfo,
27
+ writeFile,
28
+ readFile,
29
+ printError,
30
+ };
@@ -0,0 +1,12 @@
1
+ const { flow } = require('lodash/fp');
2
+ const { addHours } = require('date-fns/fp');
3
+
4
+ const computeActivationDate = ({
5
+ activatesInHours = 0 /* by default activate immediately */,
6
+ }) => {
7
+ return flow(addHours(activatesInHours))(new Date());
8
+ };
9
+
10
+ module.exports = {
11
+ computeActivationDate,
12
+ };
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ ...require('./compute-activation-date'),
3
+ ...require('./common'),
4
+ ...require('./load-csv'),
5
+ ...require('./load-handlebars-template'),
6
+ ...require('./parse-column'),
7
+ ...require('./prepare-variable-sets'),
8
+ };
@@ -0,0 +1,34 @@
1
+ const fs = require('fs');
2
+ const csv = require('csv-parser');
3
+ const stripBom = require('strip-bom-stream');
4
+ const { isString, indexOf } = require('lodash/fp');
5
+
6
+ const loadCsv = (fileName) => {
7
+ return new Promise((resolve, reject) => {
8
+ const csvRows = [];
9
+ let csvHeaders;
10
+ fs.createReadStream(fileName)
11
+ .pipe(stripBom())
12
+ .pipe(csv())
13
+ .on('headers', (headers) => {
14
+ // eslint-disable-next-line better-mutation/no-mutation
15
+ csvHeaders = headers;
16
+ })
17
+ .on('data', (data) => {
18
+ // eslint-disable-next-line better-mutation/no-mutating-methods
19
+ csvRows.push(data);
20
+ })
21
+ .on('err', (err) => reject(err))
22
+ .on('end', () => {
23
+ resolve([csvHeaders, csvRows]);
24
+ });
25
+ });
26
+ };
27
+
28
+ const getColIndex = (csvHeaders, column) =>
29
+ isString(column) ? indexOf(column, csvHeaders) : column;
30
+
31
+ const getColName = (csvHeaders, column) =>
32
+ isString(column) ? column : csvHeaders[column];
33
+
34
+ module.exports = { loadCsv, getColIndex, getColName };
@@ -0,0 +1,9 @@
1
+ const Handlebars = require('handlebars');
2
+ const { readFile } = require('./common');
3
+
4
+ const loadHandlebarsTemplate = (filename) => {
5
+ const templateString = readFile(filename);
6
+ return Handlebars.compile(templateString);
7
+ };
8
+
9
+ module.exports = { loadHandlebarsTemplate };
@@ -0,0 +1,8 @@
1
+ const parseColumn = (value) => {
2
+ const parsedValue = parseInt(value, 10);
3
+ if (Number.isNaN(parsedValue)) {
4
+ return value;
5
+ }
6
+ return parsedValue;
7
+ };
8
+ module.exports = { parseColumn };
@@ -0,0 +1,23 @@
1
+ const { map } = require('lodash/fp');
2
+ const { getColName } = require('./load-csv');
3
+
4
+ const prepareVariableSets = async (
5
+ csvHeaders,
6
+ csvRows,
7
+ { vendorUseridColumn, vars, did }
8
+ ) => {
9
+ const overrideVars = { ...vars };
10
+ if (did != null) {
11
+ overrideVars.did = did;
12
+ }
13
+ return map(
14
+ (csvRow) => ({
15
+ ...csvRow,
16
+ ...overrideVars,
17
+ vendorUserId: csvRow[getColName(csvHeaders, vendorUseridColumn)],
18
+ }),
19
+ csvRows
20
+ );
21
+ };
22
+
23
+ module.exports = { prepareVariableSets };
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('./data-loader');
@@ -0,0 +1,72 @@
1
+ # vendor-credentials command
2
+
3
+ Used for creating users and then attaching them to specific credentials.
4
+
5
+ - Supports new users only, not matching to existing users
6
+ - Credentials & People must be specified as a handlebars templates. The data is populated from two sources
7
+ - the csv specified with variable names the same as the headers
8
+ - any `-v` arg parsed in
9
+
10
+ Checkout the [test data](../test/data).
11
+
12
+ ## How to Use
13
+ `data-loader vendorcreds [options]`
14
+
15
+ ### Config
16
+
17
+ #### General config
18
+
19
+ `-c [CSV_FILENAME]` **required** parameter containing the csv file
20
+
21
+ `-p [OUTPUT_PATH]` **required** the output directory to use where QR codes and output state files are stored
22
+
23
+ `-l [LABEL]` A label to attach to the records added to the agent
24
+
25
+ `--dry-run` Run a test that parses the CSV and creates offers, but does not attempt to write the data to an agent. great for testing!
26
+
27
+ #### Issuing config
28
+
29
+ `--new` to create a new issuing configuration ("disclosure")
30
+
31
+ `-i [DISCLOSURE_ID]` an existing disclosure to use for the batch issuing
32
+
33
+ `-d [DID]` **required** the issuer's DID
34
+
35
+ `-t [URL]` **required** the url to the T&Cs that holder must consent to
36
+
37
+ `--purpose` The purpose to display to the user. Use a maximum for 64 chars. Default is "Career Credential Issuing"
38
+
39
+ `--authTokenExpiresIn` The number of minutes that the offer will be available for after activation. Default is 365 days.
40
+
41
+ #### User authentication and matching config
42
+
43
+ `-y [ID_CREDENTIAL_TYPE]` the credential type used for identifying the user. Default is Email.
44
+
45
+ `-u [COLUMN]` the column from the CSV for the user id. Value is made available as "vendorUserId" in the offer template. Default is the first column
46
+
47
+ `-m [COLUMN]` the column from the CSV for the user to be matched against the ID credential's "identifier" property. Default is the first column
48
+
49
+ #### Offer generation config
50
+
51
+ `-o [OFFER_TEMPLATE_FILENAME]` **required** offer handlebars template. Use moustaches around variables such as `{{did}}`
52
+
53
+ `--var=[VAR_NAME]=[VAR_VALUE]` variables used in the templates can be specified on the command line. They override any csv values
54
+
55
+ #### Credential Agent config
56
+
57
+ `-e [URL]` **required if not a dryrun** the endpoint of the mockvendor server
58
+
59
+ `-t [AUTH_TOKEN]` **required if not a dryrun** the bearer token to use when calling the mockvendor server
60
+
61
+ `--legacy` the target credential agent is running in the "LEGACY" offer type mode. Default is false
62
+
63
+ #### Output config
64
+
65
+ `-x` if passed an output csv is generated including the vendor's user id as the first column and the generated qrcode filename and deeplink
66
+
67
+ `--x-name [OUTPUT_CSV_NAME]` The file name for the output CSV. Default is "output"
68
+
69
+ ### Dry Run Example
70
+ Dry runs that print out what updates will be issued should omit the `-e` prop
71
+
72
+ `./data-loader vendorcreds -c ./test/data/variables.csv -o ./test/data/offer.template.json -d did:ion:sap456`
@@ -0,0 +1,32 @@
1
+ const got = require('got');
2
+ const { printInfo } = require('../helpers/common');
3
+
4
+ const setupGot = ({ endpoint, authToken }) => {
5
+ const options = {
6
+ prefixUrl: endpoint,
7
+ };
8
+ if (authToken != null) {
9
+ options.headers = { Authorization: `Bearer ${authToken}` };
10
+ }
11
+ return got.extend(options);
12
+ };
13
+
14
+ const initExecuteUpdate = (options) => {
15
+ const vendorGot = setupGot(options);
16
+ return async ({ person, offer }) => {
17
+ if (person) {
18
+ printInfo({
19
+ createdPerson: await vendorGot
20
+ .post('api/users', { json: person })
21
+ .json(),
22
+ });
23
+ }
24
+ printInfo({
25
+ createdOffer: await vendorGot.post('api/offers', { json: offer }).json(),
26
+ });
27
+ };
28
+ };
29
+
30
+ module.exports = {
31
+ initExecuteUpdate,
32
+ };
@@ -0,0 +1,49 @@
1
+ const { program } = require('commander');
2
+ const { reduce } = require('lodash/fp');
3
+ const { printInfo, parseColumn } = require('../helpers');
4
+ const { executeVendorCredentials } = require('./orchestrator');
5
+
6
+ const parseVar = reduce((acc, pair) => {
7
+ const [key, value] = pair.split('=');
8
+ acc[key] = value;
9
+ return acc;
10
+ }, {});
11
+
12
+ program
13
+ .name('data-loader vendorcreds')
14
+ .description('Loads data into a db')
15
+ .usage('[options]')
16
+ .requiredOption(
17
+ '-c, --csv-filename <filename>',
18
+ 'File name containing variables'
19
+ )
20
+ .requiredOption(
21
+ '-o, --offer-template-filename <filename>',
22
+ 'File name containing the credential template file'
23
+ )
24
+ .option(
25
+ '-p, --person-template-filename <filename>',
26
+ 'File name containing the credential template file'
27
+ )
28
+ .option(
29
+ '-e, --endpoint <url>',
30
+ 'Endpoint to call to upload the people and credentials to'
31
+ )
32
+ .option(
33
+ '-u --vendor-userid-column <vendorUseridColumn>',
34
+ `the column from the CSV that is users id. Value is made available as "vendorUserId" in the offer template. Accepts
35
+ header name or index. Default is 0.`,
36
+ parseColumn,
37
+ '0'
38
+ )
39
+ .option('-t, --auth-token <url>', 'Bearer Auth Token to use')
40
+ .option('-l, --label <label>', 'A label to attach to the documents inserted')
41
+ .option('-v, --var <var...>', 'A variable to add. use name=value')
42
+ .action(async () => {
43
+ const options = program.opts();
44
+ // eslint-disable-next-line better-mutation/no-mutation
45
+ options.vars = parseVar(options.var);
46
+ printInfo(options);
47
+ await executeVendorCredentials(options);
48
+ })
49
+ .parseAsync(process.argv);
@@ -0,0 +1,22 @@
1
+ const { initExecuteUpdate } = require('./execute-update');
2
+ const { prepareData } = require('./prepare-data');
3
+ const { loadCsv, printInfo } = require('../helpers');
4
+
5
+ // eslint-disable-next-line consistent-return
6
+ const executeVendorCredentials = async (options) => {
7
+ const [csvHeaders, csvRows] = await loadCsv(options.csvFilename);
8
+ const updates = await prepareData(csvHeaders, csvRows, options);
9
+
10
+ if (options.endpoint == null) {
11
+ printInfo(JSON.stringify({ updates }));
12
+ return updates;
13
+ }
14
+
15
+ const executeUpdate = initExecuteUpdate(options);
16
+ for (const update of updates) {
17
+ // eslint-disable-next-line no-await-in-loop
18
+ await executeUpdate(update);
19
+ }
20
+ };
21
+
22
+ module.exports = { executeVendorCredentials };
@@ -0,0 +1,36 @@
1
+ const { map } = require('lodash/fp');
2
+ const {
3
+ loadHandlebarsTemplate,
4
+ } = require('../helpers/load-handlebars-template');
5
+ const { prepareVariableSets } = require('../helpers');
6
+
7
+ const prepareData = async (csvHeaders, csvRows, options) => {
8
+ const variableSets = await prepareVariableSets(csvHeaders, csvRows, options);
9
+
10
+ const { offerTemplateFilename, personTemplateFilename, label } = options;
11
+ const offerTemplate = loadHandlebarsTemplate(offerTemplateFilename);
12
+ const personTemplate = personTemplateFilename
13
+ ? loadHandlebarsTemplate(personTemplateFilename)
14
+ : undefined;
15
+ return map((variableSet) => {
16
+ return {
17
+ offer: prepareDocument({ template: offerTemplate, variableSet, label }),
18
+ person: personTemplate
19
+ ? prepareDocument({ template: personTemplate, variableSet, label })
20
+ : undefined,
21
+ };
22
+ }, variableSets);
23
+ };
24
+
25
+ const prepareDocument = ({ template, variableSet, label }) => {
26
+ const offerString = template(variableSet);
27
+ const json = JSON.parse(offerString);
28
+ return {
29
+ ...json,
30
+ label,
31
+ };
32
+ };
33
+
34
+ module.exports = {
35
+ prepareData,
36
+ };