@velocitycareerlabs/data-loader 1.20.0-dev-build.1cb592b8f → 1.20.0-dev-build.1e6832e17

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.
@@ -1,60 +1,21 @@
1
- const { get, includes, isNil, map, omitBy, values } = require('lodash/fp');
1
+ const { get, includes, isNil, map, omitBy, isEmpty } = require('lodash/fp');
2
2
  const { formatISO } = require('date-fns/fp');
3
3
  const { nanoid } = require('nanoid');
4
- const {
5
- loadHandlebarsTemplate,
6
- loadCsvVariableSets,
7
- computeActivationDate,
8
- } = require('../helpers');
9
- const { DisclosureType } = require('./constants');
4
+ const { loadHandlebarsTemplate, computeActivationDate } = require('../helpers');
5
+ const { idCredentialTypeToPick } = require('./constants');
6
+ const { getColIndex, prepareVariableSets } = require('../helpers');
10
7
 
11
8
  const EMAIL_REGEX =
12
9
  // eslint-disable-next-line max-len
13
10
  /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
14
-
15
11
  const PHONE_REGEX = /\(?([0-9]{3})\)?([ .-]?)([0-9]{3})\2([0-9]{4})/;
16
- const VENDOR_USER_ID_PATH = 'credentialSubject.vendorUserId';
17
-
18
- const CREDENTIAL_TYPES = {
19
- EMAIL: 'EmailV1.0',
20
- PHONE: 'PhoneV1.0',
21
- DRIVER_LICENSE: 'DriversLicenseV1.0',
22
- ID_DOCUMENT: 'IdDocumentV1.0',
23
- NATIONAL_ID_CARD: 'NationalIdCardV1.0',
24
- PASSPORT: 'PassportV1.0',
25
- PROOF_OF_AGE: 'ProofOfAgeV1.0',
26
- RESIDENT_PERMIT: 'ResidentPermitV1.0',
27
- };
28
-
29
- const DEFAULT_ID_DOCUMENT_CREDENTIALS_PATH =
30
- '$.idDocumentCredentials[*].credentialSubject.identifier';
31
- const credentialTypeToPick = {
32
- [CREDENTIAL_TYPES.EMAIL]: '$.emails',
33
- [CREDENTIAL_TYPES.PHONE]: '$.phones',
34
- [CREDENTIAL_TYPES.DRIVER_LICENSE]: DEFAULT_ID_DOCUMENT_CREDENTIALS_PATH,
35
- [CREDENTIAL_TYPES.ID_DOCUMENT]: DEFAULT_ID_DOCUMENT_CREDENTIALS_PATH,
36
- [CREDENTIAL_TYPES.NATIONAL_ID_CARD]: DEFAULT_ID_DOCUMENT_CREDENTIALS_PATH,
37
- [CREDENTIAL_TYPES.PASSPORT]: DEFAULT_ID_DOCUMENT_CREDENTIALS_PATH,
38
- [CREDENTIAL_TYPES.PROOF_OF_AGE]: DEFAULT_ID_DOCUMENT_CREDENTIALS_PATH,
39
- [CREDENTIAL_TYPES.RESIDENT_PERMIT]: DEFAULT_ID_DOCUMENT_CREDENTIALS_PATH,
40
- };
41
12
 
42
- const prepareData = async (
43
- disclosureType,
44
- { csvFilename, offerTemplateFilename, vars: defaultVars, did, ...options }
45
- ) => {
46
- validateCredentialType(options.credentialType);
47
- const newDisclosureRequest = prepareNewDisclosureRequest(
48
- disclosureType,
49
- options
50
- );
13
+ const VENDOR_USER_ID_PATH = 'credentialSubject.vendorUserId';
51
14
 
52
- const variableSets = await loadCsvVariableSets(csvFilename, {
53
- ...defaultVars,
54
- did,
55
- });
56
- const offerTemplate = loadHandlebarsTemplate(offerTemplateFilename);
57
- const newExchangeOffers = map(
15
+ const prepareExchangeOffers = async (csvHeaders, csvRows, options) => {
16
+ const variableSets = await prepareVariableSets(csvHeaders, csvRows, options);
17
+ const offerTemplate = loadHandlebarsTemplate(options.offerTemplateFilename);
18
+ return map(
58
19
  (variableSet) => ({
59
20
  newOffer: prepareNewOffer({
60
21
  template: offerTemplate,
@@ -65,15 +26,13 @@ const prepareData = async (
65
26
  }),
66
27
  variableSets
67
28
  );
68
-
69
- return { newDisclosureRequest, newExchangeOffers };
70
29
  };
71
30
 
72
31
  const prepareNewOffer = ({ template, variableSet, label }) => {
73
32
  validateVariableSet(variableSet);
74
33
  const offerString = template(variableSet);
75
34
  const offer = JSON.parse(offerString);
76
- validateVendorUserId(offer, variableSet);
35
+ validateVendorUserIdInOffer(offer, variableSet);
77
36
  return omitBy(isNil, {
78
37
  ...offer,
79
38
  offerId: variableSet.offerId ?? nanoid(),
@@ -81,15 +40,6 @@ const prepareNewOffer = ({ template, variableSet, label }) => {
81
40
  });
82
41
  };
83
42
 
84
- const validateCredentialType = (credentialType) => {
85
- const allowedCredentialTypes = values(CREDENTIAL_TYPES);
86
- if (!credentialType || !allowedCredentialTypes.includes(credentialType)) {
87
- throw new Error(
88
- `${credentialType} doesn't exist. Please use one of ${allowedCredentialTypes}`
89
- );
90
- }
91
- };
92
-
93
43
  const validateVariableSet = (variableSet) => {
94
44
  if (
95
45
  variableSet.email == null &&
@@ -99,13 +49,22 @@ const validateVariableSet = (variableSet) => {
99
49
  throw new Error('"email", "phone", or "identifier" column must be defined');
100
50
  }
101
51
 
102
- const { email, phone } = variableSet;
52
+ const { email, phone, vendorUserId } = variableSet;
103
53
 
104
- validateEmail(email);
105
- validatePhone(phone);
54
+ validateVendorUserIdVariable(vendorUserId);
55
+ validateEmailVariable(email);
56
+ validatePhoneVariable(phone);
106
57
  };
107
58
 
108
- const validateEmail = (email) => {
59
+ const validateVendorUserIdVariable = (vendorUserId) => {
60
+ if (isEmpty(vendorUserId)) {
61
+ throw new Error(
62
+ 'vendorUserId variable must exist. Use the -u <column> option'
63
+ );
64
+ }
65
+ };
66
+
67
+ const validateEmailVariable = (email) => {
109
68
  if (!email) return;
110
69
 
111
70
  if (!EMAIL_REGEX.test(email)) {
@@ -113,7 +72,7 @@ const validateEmail = (email) => {
113
72
  }
114
73
  };
115
74
 
116
- const validatePhone = (phone) => {
75
+ const validatePhoneVariable = (phone) => {
117
76
  if (!phone) return;
118
77
 
119
78
  if (!PHONE_REGEX.test(phone)) {
@@ -121,7 +80,7 @@ const validatePhone = (phone) => {
121
80
  }
122
81
  };
123
82
 
124
- const validateVendorUserId = (offer, variableSet) => {
83
+ const validateVendorUserIdInOffer = (offer, variableSet) => {
125
84
  const value = get(VENDOR_USER_ID_PATH, offer);
126
85
  if (!includes(value, variableSet)) {
127
86
  throw new Error(
@@ -132,7 +91,7 @@ const validateVendorUserId = (offer, variableSet) => {
132
91
  }
133
92
  };
134
93
 
135
- const matcherToCredentialType = (credentialType, variableSet) => {
94
+ const matcherToIdCredentialType = (idCredentialType, variableSet) => {
136
95
  const typeToVariable = {
137
96
  'EmailV1.0': variableSet.email,
138
97
  'PhoneV1.0': variableSet.phone,
@@ -143,32 +102,31 @@ const matcherToCredentialType = (credentialType, variableSet) => {
143
102
  'ProofOfAgeV1.0': variableSet.identifier,
144
103
  'ResidentPermitV1.0': variableSet.identifier,
145
104
  };
146
- return typeToVariable[credentialType] || variableSet.email;
105
+ return typeToVariable[idCredentialType] || variableSet.email;
147
106
  };
148
107
 
149
- const prepareNewExchange = ({ variableSet, label, credentialType }) =>
108
+ const prepareNewExchange = ({ variableSet, label, idCredentialType }) =>
150
109
  omitBy(isNil, {
151
110
  type: 'ISSUING',
152
111
  identityMatcherValues: [
153
- matcherToCredentialType(credentialType, variableSet),
112
+ matcherToIdCredentialType(idCredentialType, variableSet),
154
113
  ],
155
114
  label,
156
115
  });
157
116
 
158
117
  const prepareNewDisclosureRequest = (
159
- disclosureType,
118
+ csvHeaders,
160
119
  {
161
120
  label,
162
121
  termsUrl,
163
122
  purpose,
164
- credentialType,
165
- authTokenExpiresIn = '10080',
123
+ idCredentialType,
124
+ identifierMatchColumn,
125
+ vendorUseridColumn,
126
+ authTokenExpiresIn,
166
127
  ...options
167
128
  }
168
129
  ) => {
169
- if (disclosureType !== DisclosureType.NEW) {
170
- return undefined;
171
- }
172
130
  const activationDate = computeActivationDate(options);
173
131
 
174
132
  const newDisclosureRequest = {
@@ -177,18 +135,18 @@ const prepareNewDisclosureRequest = (
177
135
  vendorEndpoint: 'integrated-issuing-identification',
178
136
  types: [
179
137
  {
180
- type: credentialType,
138
+ type: idCredentialType,
181
139
  },
182
140
  ],
183
141
  identityMatchers: {
184
142
  rules: [
185
143
  {
186
- valueIndex: 0,
187
- path: [credentialTypeToPick[credentialType]],
144
+ valueIndex: getColIndex(csvHeaders, identifierMatchColumn),
145
+ path: [idCredentialTypeToPick[idCredentialType]],
188
146
  rule: 'pick',
189
147
  },
190
148
  ],
191
- vendorUserIdIndex: 0,
149
+ vendorUserIdIndex: getColIndex(csvHeaders, vendorUseridColumn),
192
150
  },
193
151
  setIssuingDefault: true,
194
152
  duration: '1h', // 1 hour by default
@@ -204,5 +162,6 @@ const prepareNewDisclosureRequest = (
204
162
  };
205
163
 
206
164
  module.exports = {
207
- prepareData,
165
+ prepareNewDisclosureRequest,
166
+ prepareExchangeOffers,
208
167
  };
@@ -1,8 +1,8 @@
1
1
  const { existsSync } = require('fs');
2
2
 
3
- const validateDirectoryExists = ({ path }) => {
4
- if (!existsSync(path)) {
5
- throw new Error(`${path} does not exist`);
3
+ const validateDirectoryExists = (options) => {
4
+ if (options.dryrun == null && !existsSync(options.path)) {
5
+ throw new Error('Path does not exist. Check the -p var');
6
6
  }
7
7
  };
8
8
 
@@ -3,4 +3,6 @@ module.exports = {
3
3
  ...require('./common'),
4
4
  ...require('./load-csv'),
5
5
  ...require('./load-handlebars-template'),
6
+ ...require('./parse-column'),
7
+ ...require('./prepare-variable-sets'),
6
8
  };
@@ -1,29 +1,32 @@
1
1
  const fs = require('fs');
2
2
  const csv = require('csv-parser');
3
- const { map } = require('lodash/fp');
3
+ const { isString, indexOf } = require('lodash/fp');
4
4
 
5
5
  const loadCsv = (fileName) => {
6
6
  return new Promise((resolve, reject) => {
7
- const results = [];
7
+ const csvRows = [];
8
+ let csvHeaders;
8
9
  fs.createReadStream(fileName)
9
10
  .pipe(csv())
11
+ .on('headers', (headers) => {
12
+ // eslint-disable-next-line better-mutation/no-mutation
13
+ csvHeaders = headers;
14
+ })
10
15
  .on('data', (data) => {
11
16
  // eslint-disable-next-line better-mutation/no-mutating-methods
12
- results.push(data);
17
+ csvRows.push(data);
13
18
  })
14
19
  .on('err', (err) => reject(err))
15
20
  .on('end', () => {
16
- resolve(results);
21
+ resolve([csvHeaders, csvRows]);
17
22
  });
18
23
  });
19
24
  };
20
25
 
21
- const mergeRowsWithDefaults = (defaultVars) =>
22
- map((csvRow) => ({ ...csvRow, ...defaultVars }));
26
+ const getColIndex = (csvHeaders, column) =>
27
+ isString(column) ? indexOf(column, csvHeaders) : column;
23
28
 
24
- const loadCsvVariableSets = async (fileName, defaultVars) => {
25
- const csvRows = await loadCsv(fileName);
26
- return mergeRowsWithDefaults(defaultVars)(csvRows);
27
- };
29
+ const getColName = (csvHeaders, column) =>
30
+ isString(column) ? column : csvHeaders[column];
28
31
 
29
- module.exports = { loadCsv, loadCsvVariableSets, mergeRowsWithDefaults };
32
+ module.exports = { loadCsv, getColIndex, getColName };
@@ -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 };
@@ -11,22 +11,62 @@ Checkout the [test data](../test/data).
11
11
 
12
12
  ## How to Use
13
13
  `data-loader vendorcreds [options]`
14
- ### Options
15
14
 
16
- `-c [CSV_FILENAME]` required parameter containing the csv file
15
+ ### Config
17
16
 
18
- `-o [OFFER_TEMPLATE_FILENAME]` offer handlebars template. Use moustaches around variables such as `{{did}}`
17
+ #### General config
19
18
 
20
- `-p [PERSON_TEMPLATE_FILENAME]` user handlebars template. Use moustaches around variables such as `{{email}}`.
21
- If -p is left out, no users will be created; only offers will be created.
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}}`
22
52
 
23
53
  `--var=[VAR_NAME]=[VAR_VALUE]` variables used in the templates can be specified on the command line. They override any csv values
24
54
 
25
- `-e [URL]` the endpoint of the mockvendor server
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
26
66
 
27
- `-t [AUTH_TOKEN]` the bearer token to use when calling the mockvendor server
67
+ `--x-name [OUTPUT_CSV_NAME]` The file name for the output CSV. Default is "output"
28
68
 
29
69
  ### Dry Run Example
30
70
  Dry runs that print out what updates will be issued should omit the `-e` prop
31
71
 
32
- `./data-loader vendorcreds -c ./test/data/variables.csv -o ./test/data/offer.template.json -p ./test/data/person.template.json --var=did=did:ion:sap456`
72
+ `./data-loader vendorcreds -c ./test/data/variables.csv -o ./test/data/offer.template.json -d did:ion:sap456`
@@ -1,10 +1,7 @@
1
- /* eslint-disable no-await-in-loop */
2
-
3
1
  const { program } = require('commander');
4
2
  const { reduce } = require('lodash/fp');
5
- const { prepareData } = require('./prepare-data');
6
- const { printInfo } = require('../helpers/common');
7
- const { initExecuteUpdate } = require('./execute-update');
3
+ const { printInfo, parseColumn } = require('../helpers');
4
+ const { executeVendorCredentials } = require('./orchestrator');
8
5
 
9
6
  const parseVar = reduce((acc, pair) => {
10
7
  const [key, value] = pair.split('=');
@@ -32,6 +29,13 @@ program
32
29
  '-e, --endpoint <url>',
33
30
  'Endpoint to call to upload the people and credentials to'
34
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
+ )
35
39
  .option('-t, --auth-token <url>', 'Bearer Auth Token to use')
36
40
  .option('-l, --label <label>', 'A label to attach to the documents inserted')
37
41
  .option('-v, --var <var...>', 'A variable to add. use name=value')
@@ -40,14 +44,6 @@ program
40
44
  // eslint-disable-next-line better-mutation/no-mutation
41
45
  options.vars = parseVar(options.var);
42
46
  printInfo(options);
43
- const executeUpdate = initExecuteUpdate(options);
44
- const updates = await prepareData(options);
45
- if (options.endpoint != null) {
46
- for (const update of updates) {
47
- await executeUpdate(update);
48
- }
49
- } else {
50
- printInfo(JSON.stringify({ updates }));
51
- }
47
+ await executeVendorCredentials(options);
52
48
  })
53
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 };
@@ -2,16 +2,12 @@ const { map } = require('lodash/fp');
2
2
  const {
3
3
  loadHandlebarsTemplate,
4
4
  } = require('../helpers/load-handlebars-template');
5
- const { loadCsvVariableSets } = require('../helpers/load-csv');
5
+ const { prepareVariableSets } = require('../helpers');
6
6
 
7
- const prepareData = async ({
8
- csvFilename,
9
- offerTemplateFilename,
10
- personTemplateFilename,
11
- label,
12
- vars: defaultVars,
13
- }) => {
14
- const variableSets = await loadCsvVariableSets(csvFilename, defaultVars);
7
+ const prepareData = async (csvHeaders, csvRows, options) => {
8
+ const variableSets = await prepareVariableSets(csvHeaders, csvRows, options);
9
+
10
+ const { offerTemplateFilename, personTemplateFilename, label } = options;
15
11
  const offerTemplate = loadHandlebarsTemplate(offerTemplateFilename);
16
12
  const personTemplate = personTemplateFilename
17
13
  ? loadHandlebarsTemplate(personTemplateFilename)