@velocitycareerlabs/data-loader 1.19.0-dev-build.11b7cc7b6

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 (51) hide show
  1. package/LICENSE +248 -0
  2. package/jest.config.js +7 -0
  3. package/package.json +43 -0
  4. package/src/batch-issuing/README.md +95 -0
  5. package/src/batch-issuing/constants.js +8 -0
  6. package/src/batch-issuing/fetchers.js +100 -0
  7. package/src/batch-issuing/file-readers.js +15 -0
  8. package/src/batch-issuing/file-writers.js +37 -0
  9. package/src/batch-issuing/index.js +115 -0
  10. package/src/batch-issuing/orchestrators.js +258 -0
  11. package/src/batch-issuing/prepare-data.js +208 -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 +6 -0
  18. package/src/helpers/load-csv.js +29 -0
  19. package/src/helpers/load-handlebars-template.js +9 -0
  20. package/src/index.js +3 -0
  21. package/src/vendor-credentials/README.md +32 -0
  22. package/src/vendor-credentials/execute-update.js +32 -0
  23. package/src/vendor-credentials/index.js +53 -0
  24. package/src/vendor-credentials/prepare-data.js +40 -0
  25. package/test/batch-issuing.test.js +916 -0
  26. package/test/data/badge-offer.template.json +22 -0
  27. package/test/data/batch-vars-offerids.csv +3 -0
  28. package/test/data/batch-vars.csv +3 -0
  29. package/test/data/batchissuing-variables.csv +3 -0
  30. package/test/data/driver-license-offer.template.json +10 -0
  31. package/test/data/driver-license-variables.csv +3 -0
  32. package/test/data/email-offer.template.json +10 -0
  33. package/test/data/email-variables.csv +3 -0
  34. package/test/data/id-document-offer.template.json +10 -0
  35. package/test/data/id-document-variables.csv +3 -0
  36. package/test/data/national-id-card-offer.template.json +10 -0
  37. package/test/data/national-id-card-variables.csv +3 -0
  38. package/test/data/offer.template.json +22 -0
  39. package/test/data/passport-offer.template.json +10 -0
  40. package/test/data/passport-variables.csv +3 -0
  41. package/test/data/person.template.json +15 -0
  42. package/test/data/phone-offer.template.json +10 -0
  43. package/test/data/phone-variables.csv +2 -0
  44. package/test/data/phones-batch-vars-offerids.csv +3 -0
  45. package/test/data/proof-of-age-offer.template.json +10 -0
  46. package/test/data/proof-of-age-variables.csv +3 -0
  47. package/test/data/resident-permit-offer.template.json +10 -0
  48. package/test/data/resident-permit-variables.csv +3 -0
  49. package/test/data/variables-no-did.csv +3 -0
  50. package/test/data/variables.csv +3 -0
  51. package/test/vendor-credentials.test.js +225 -0
@@ -0,0 +1,115 @@
1
+ const { program } = require('commander');
2
+ const { reduce } = require('lodash/fp');
3
+ const { printInfo, printError } = require('../helpers/common');
4
+ const { runBatchIssuing } = require('./orchestrators');
5
+
6
+ const parseVar = reduce((acc, pair) => {
7
+ const [key, value] = pair.split('=');
8
+ acc[key] = value;
9
+ return acc;
10
+ }, {});
11
+
12
+ const validateOptions = (options) => {
13
+ if (options.dryrun == null && options.endpoint == null) {
14
+ throw new Error(
15
+ '"-e" or "--endpoint" is required unless executing a "dryrun"'
16
+ );
17
+ }
18
+ if (options.endpoint != null && options.authToken == null) {
19
+ throw new Error('"-a" or "--auth-token" is required');
20
+ }
21
+ };
22
+
23
+ program
24
+ .name('data-loader vendorcreds')
25
+ .description('Loads data into a db')
26
+ .usage('[options]')
27
+ .requiredOption(
28
+ '-c, --csv-filename <filename>',
29
+ 'File name containing variables'
30
+ )
31
+ .requiredOption(
32
+ '-o, --offer-template-filename <filename>',
33
+ 'File name containing the credential template file'
34
+ )
35
+ .requiredOption(
36
+ '-d, --did <did>',
37
+ 'DID of the organization that should do the issuing'
38
+ )
39
+ .requiredOption(
40
+ '-p, --path <path>',
41
+ 'path of directory for the qr-code images and output CSV'
42
+ )
43
+ .requiredOption(
44
+ '-t, --terms-url <termsUrl>',
45
+ 'The terms and conditions url for the holder to accept'
46
+ )
47
+ .option(
48
+ '--legacy',
49
+ 'the target credential agent is running in the "LEGACY" offer type mode. Default is false'
50
+ )
51
+ .option(
52
+ '-x --outputcsv',
53
+ "if passed an output csv is generated including the vendor's user id as the first column and the generated qrcode filename and deeplink"
54
+ )
55
+ .option(
56
+ '--x-name <outputCsvName>',
57
+ 'The file name for the output CSV. Default is "output"'
58
+ )
59
+ .option(
60
+ '--x-vendor-userid-name <vendorUserIdName>',
61
+ 'The column name to use for the vendor user id in the output csv. Default is "email"'
62
+ )
63
+ .option(
64
+ '--dryrun',
65
+ 'if passed in then a dry run executes showing how the data will be formatted'
66
+ )
67
+ .option(
68
+ '-e, --endpoint <url>',
69
+ 'Credential Agent Endpoint to call to execute the issuing'
70
+ )
71
+ .option('-a, --auth-token <url>', 'Bearer Auth Token to use')
72
+ .option('-l, --label <label>', 'A label to attach to the documents inserted')
73
+ .option('-v, --var <var...>', 'A variable to add. use name=value')
74
+ .option(
75
+ '-y, --credential-type <credentialType>',
76
+ 'The type of credentials. Default is EmailV1.0.',
77
+ 'EmailV1.0'
78
+ )
79
+ .option(
80
+ '--purpose <purpose>',
81
+ 'The purpose to display to the user. Use a maximum for 64 chars. Default is "Career Credential Issuing"'
82
+ )
83
+ .option(
84
+ '--authTokenExpiresIn <authTokenExpiresIn>',
85
+ 'The number of minutes that the offer will be available for after activation. Default is 90 days.',
86
+ '10080'
87
+ )
88
+ .option('--new', 'Use a new disclosure for batch issuing')
89
+ .option(
90
+ '-i, --disclosure [disclosure]',
91
+ 'An existing disclosure to use for the batch issuing'
92
+ )
93
+ .action(async () => {
94
+ const options = program.opts();
95
+ validateOptions(options);
96
+ // eslint-disable-next-line better-mutation/no-mutation
97
+ options.vars = parseVar(options.var);
98
+ printInfo(options);
99
+ try {
100
+ await runBatchIssuing(options);
101
+ } catch (error) {
102
+ printError('Batch Issuing Script Failure');
103
+ if (error.response) {
104
+ printError({
105
+ error: error.message,
106
+ statusCode: error.response.statusCode,
107
+ errorCode: error.response.errorCode,
108
+ response: error.response.body,
109
+ });
110
+ } else {
111
+ printError(error);
112
+ }
113
+ }
114
+ })
115
+ .parseAsync(process.argv);
@@ -0,0 +1,258 @@
1
+ const { omitBy, isNil, find, isString, isEmpty } = require('lodash/fp');
2
+ const { validateDirectoryExists } = require('./validate-directory-exists');
3
+ const { initFetchers } = require('./fetchers');
4
+ const { prepareData } = require('./prepare-data');
5
+ const { printInfo } = require('../helpers/common');
6
+ const {
7
+ writeQrCodeFile,
8
+ writeJsonFile,
9
+ writeOutputCsv,
10
+ } = require('./file-writers');
11
+ const { DisclosureType } = require('./constants');
12
+ const {
13
+ askDisclosureType,
14
+ askDisclosureList,
15
+ askUseNewDisclosure,
16
+ } = require('./prompts');
17
+
18
+ // eslint-disable-next-line complexity
19
+ const getDisclosureType = async (hasIntegratedDisclosures, options) => {
20
+ const isIntegratedMode = !options.new && !options.disclosure;
21
+
22
+ switch (true) {
23
+ case isIntegratedMode && !hasIntegratedDisclosures: {
24
+ const useNewDisclosure = await askUseNewDisclosure();
25
+ return useNewDisclosure ? DisclosureType.NEW : null;
26
+ }
27
+ case isIntegratedMode && hasIntegratedDisclosures:
28
+ return askDisclosureType();
29
+ case Boolean(options.disclosure):
30
+ return DisclosureType.EXISTING;
31
+ default:
32
+ return DisclosureType.NEW;
33
+ }
34
+ };
35
+
36
+ const runBatchIssuing = async (options) => {
37
+ validateDirectoryExists(options);
38
+ const fetchers = initFetchers(options);
39
+ const disclosures = await getIntegratedDisclosures(fetchers);
40
+ const disclosureType = await getDisclosureType(
41
+ !isEmpty(disclosures),
42
+ options
43
+ );
44
+
45
+ if (isEmpty(disclosureType)) {
46
+ return;
47
+ }
48
+
49
+ const { newDisclosureRequest, newExchangeOffers } = await prepareData(
50
+ disclosureType,
51
+ options
52
+ );
53
+
54
+ if (options.endpoint == null) {
55
+ printInfo('Dry Run would create:');
56
+ printInfo(
57
+ JSON.stringify(
58
+ omitBy(isNil, { newDisclosureRequest, newExchangeOffers }),
59
+ 0,
60
+ 2
61
+ )
62
+ );
63
+ return;
64
+ }
65
+
66
+ const disclosure = await createOrGotDisclosure({
67
+ disclosureType,
68
+ disclosures,
69
+ newDisclosureRequest,
70
+ fetchers,
71
+ options,
72
+ });
73
+
74
+ const disclosureRequest = await writeDisclosureToJson(disclosure, options);
75
+
76
+ const outputs = options.legacy
77
+ ? await runLegacyBatchIssuing(
78
+ disclosureRequest,
79
+ newExchangeOffers,
80
+ fetchers,
81
+ options
82
+ )
83
+ : await runSingleQrCodeBatchIssuing(
84
+ disclosureRequest,
85
+ newExchangeOffers,
86
+ fetchers,
87
+ options
88
+ );
89
+
90
+ writeOutput(outputs, options);
91
+ };
92
+
93
+ const writeOutput = (outputs, options) => {
94
+ if (options.outputcsv && options.legacy) {
95
+ writeOutputCsv(outputs, options);
96
+ }
97
+ };
98
+
99
+ const runLegacyBatchIssuing = async (
100
+ disclosureRequest,
101
+ newExchangeOffers,
102
+ fetchers,
103
+ options
104
+ ) => {
105
+ const outputs = [];
106
+ for (const newExchangeOffer of newExchangeOffers) {
107
+ outputs.push(
108
+ // eslint-disable-next-line no-await-in-loop
109
+ await createOfferExchangeAndQrCode({
110
+ ...newExchangeOffer,
111
+ disclosureRequest,
112
+ fetchers,
113
+ options,
114
+ })
115
+ );
116
+ }
117
+
118
+ return outputs;
119
+ };
120
+
121
+ const runSingleQrCodeBatchIssuing = async (
122
+ disclosureRequest,
123
+ newExchangeOffers,
124
+ fetchers,
125
+ options
126
+ ) => {
127
+ const outputs = [];
128
+ for (const newExchangeOffer of newExchangeOffers) {
129
+ outputs.push(
130
+ // eslint-disable-next-line no-await-in-loop
131
+ await createOfferExchange({
132
+ ...newExchangeOffer,
133
+ disclosureRequest,
134
+ fetchers,
135
+ options,
136
+ })
137
+ );
138
+ }
139
+
140
+ printInfo('Generating qrcode & deep link');
141
+ const deeplink = await fetchers.loadDisclosureDeeplink(disclosureRequest);
142
+ printInfo(`Deep link: ${deeplink}`);
143
+ const qrcode = await fetchers.loadDisclosureQrcode(disclosureRequest);
144
+ const { filePath } = await writeQrCodeFile('qrcode-generic', qrcode, options);
145
+ printInfo(`QRCode saved: ${filePath}`);
146
+
147
+ return outputs;
148
+ };
149
+
150
+ const getIntegratedDisclosures = async (fetchers) => {
151
+ const vendorEndpoints = ['integrated-issuing-identification'];
152
+ const disclosures = await fetchers.getDisclosureList(vendorEndpoints);
153
+ return disclosures;
154
+ };
155
+
156
+ const getDisclosure = async (disclosures, fetchers, options) => {
157
+ const { disclosure } = options;
158
+
159
+ if (isString(disclosure)) {
160
+ return fetchers.getDisclosure(disclosure);
161
+ }
162
+
163
+ const disclosureId = await askDisclosureList(disclosures);
164
+ return find(({ id }) => id === disclosureId, disclosures);
165
+ };
166
+
167
+ const writeDisclosureToJson = async (disclosureRequest, options) => {
168
+ printInfo(`Using disclosureId:${disclosureRequest.id}`);
169
+ printInfo('');
170
+
171
+ const { filePath: disclosureFilePath } = await writeJsonFile(
172
+ disclosureRequest,
173
+ `disclosure-${disclosureRequest.id}`,
174
+ options
175
+ );
176
+ await writeJsonFile(
177
+ {
178
+ disclosureId: disclosureRequest.id,
179
+ disclosureFile: disclosureFilePath,
180
+ timestamp: new Date().toISOString(),
181
+ ...options,
182
+ },
183
+ 'lastrun',
184
+ options
185
+ );
186
+ return disclosureRequest;
187
+ };
188
+
189
+ const createOfferExchangeAndQrCode = async ({
190
+ newExchange,
191
+ newOffer,
192
+ disclosureRequest,
193
+ options,
194
+ fetchers,
195
+ }) => {
196
+ const { exchange, offer, vendorUserId } = await createOfferExchange({
197
+ newExchange,
198
+ newOffer,
199
+ disclosureRequest,
200
+ fetchers,
201
+ });
202
+
203
+ const deeplink = await fetchers.loadExchangeDeeplink(exchange);
204
+ const qrcode = await fetchers.loadExchangeQrcode(exchange);
205
+ const { filePath } = await writeQrCodeFile(
206
+ `qrcode-${vendorUserId}`,
207
+ qrcode,
208
+ options
209
+ );
210
+ printInfo(`${vendorUserId} Done. Qrcode file:${filePath}`);
211
+ printInfo('');
212
+
213
+ return {
214
+ exchange,
215
+ offer,
216
+ qrcode,
217
+ qrcodeFilePath: filePath,
218
+ deeplink,
219
+ vendorUserId,
220
+ };
221
+ };
222
+
223
+ const createOfferExchange = async ({
224
+ newExchange,
225
+ newOffer,
226
+ disclosureRequest,
227
+ fetchers,
228
+ }) => {
229
+ const { vendorUserId } = newOffer.credentialSubject;
230
+ printInfo(`Setting up vendorUserId:${vendorUserId}`);
231
+ const exchange = await fetchers.createOfferExchange({
232
+ ...newExchange,
233
+ disclosureId: disclosureRequest.id,
234
+ });
235
+ const offer = await fetchers.createOffer(exchange, newOffer);
236
+ await fetchers.submitCompleteOffer(exchange, [offer]);
237
+ return {
238
+ exchange,
239
+ offer,
240
+ vendorUserId,
241
+ };
242
+ };
243
+
244
+ const createOrGotDisclosure = async ({
245
+ disclosureType,
246
+ disclosures,
247
+ newDisclosureRequest,
248
+ fetchers,
249
+ options,
250
+ }) => {
251
+ const disclosure =
252
+ disclosureType === DisclosureType.NEW
253
+ ? await fetchers.createDisclosure(newDisclosureRequest)
254
+ : await getDisclosure(disclosures, fetchers, options);
255
+ return disclosure;
256
+ };
257
+
258
+ module.exports = { runBatchIssuing };
@@ -0,0 +1,208 @@
1
+ const { get, includes, isNil, map, omitBy, values } = require('lodash/fp');
2
+ const { formatISO } = require('date-fns/fp');
3
+ const { nanoid } = require('nanoid');
4
+ const {
5
+ loadHandlebarsTemplate,
6
+ loadCsvVariableSets,
7
+ computeActivationDate,
8
+ } = require('../helpers');
9
+ const { DisclosureType } = require('./constants');
10
+
11
+ const EMAIL_REGEX =
12
+ // eslint-disable-next-line max-len
13
+ /^(([^<>()[\]\\.,;:\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
+ 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
+
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
+ );
51
+
52
+ const variableSets = await loadCsvVariableSets(csvFilename, {
53
+ ...defaultVars,
54
+ did,
55
+ });
56
+ const offerTemplate = loadHandlebarsTemplate(offerTemplateFilename);
57
+ const newExchangeOffers = map(
58
+ (variableSet) => ({
59
+ newOffer: prepareNewOffer({
60
+ template: offerTemplate,
61
+ variableSet,
62
+ ...options,
63
+ }),
64
+ newExchange: prepareNewExchange({ variableSet, ...options }),
65
+ }),
66
+ variableSets
67
+ );
68
+
69
+ return { newDisclosureRequest, newExchangeOffers };
70
+ };
71
+
72
+ const prepareNewOffer = ({ template, variableSet, label }) => {
73
+ validateVariableSet(variableSet);
74
+ const offerString = template(variableSet);
75
+ const offer = JSON.parse(offerString);
76
+ validateVendorUserId(offer, variableSet);
77
+ return omitBy(isNil, {
78
+ ...offer,
79
+ offerId: variableSet.offerId ?? nanoid(),
80
+ label,
81
+ });
82
+ };
83
+
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
+ const validateVariableSet = (variableSet) => {
94
+ if (
95
+ variableSet.email == null &&
96
+ variableSet.phone == null &&
97
+ variableSet.identifier == null
98
+ ) {
99
+ throw new Error('"email", "phone", or "identifier" column must be defined');
100
+ }
101
+
102
+ const { email, phone } = variableSet;
103
+
104
+ validateEmail(email);
105
+ validatePhone(phone);
106
+ };
107
+
108
+ const validateEmail = (email) => {
109
+ if (!email) return;
110
+
111
+ if (!EMAIL_REGEX.test(email)) {
112
+ throw new Error(`${email} is not a valid email`);
113
+ }
114
+ };
115
+
116
+ const validatePhone = (phone) => {
117
+ if (!phone) return;
118
+
119
+ if (!PHONE_REGEX.test(phone)) {
120
+ throw new Error(`${phone} is not a valid phone`);
121
+ }
122
+ };
123
+
124
+ const validateVendorUserId = (offer, variableSet) => {
125
+ const value = get(VENDOR_USER_ID_PATH, offer);
126
+ if (!includes(value, variableSet)) {
127
+ throw new Error(
128
+ `${VENDOR_USER_ID_PATH}: ${value} cannot be hardcoded and must be defined in ${JSON.stringify(
129
+ variableSet
130
+ )})`
131
+ );
132
+ }
133
+ };
134
+
135
+ const matcherToCredentialType = (credentialType, variableSet) => {
136
+ const typeToVariable = {
137
+ 'EmailV1.0': variableSet.email,
138
+ 'PhoneV1.0': variableSet.phone,
139
+ 'DriversLicenseV1.0': variableSet.identifier,
140
+ 'IdDocumentV1.0': variableSet.identifier,
141
+ 'NationalIdCardV1.0': variableSet.identifier,
142
+ 'PassportV1.0': variableSet.identifier,
143
+ 'ProofOfAgeV1.0': variableSet.identifier,
144
+ 'ResidentPermitV1.0': variableSet.identifier,
145
+ };
146
+ return typeToVariable[credentialType] || variableSet.email;
147
+ };
148
+
149
+ const prepareNewExchange = ({ variableSet, label, credentialType }) =>
150
+ omitBy(isNil, {
151
+ type: 'ISSUING',
152
+ identityMatcherValues: [
153
+ matcherToCredentialType(credentialType, variableSet),
154
+ ],
155
+ label,
156
+ });
157
+
158
+ const prepareNewDisclosureRequest = (
159
+ disclosureType,
160
+ {
161
+ label,
162
+ termsUrl,
163
+ purpose,
164
+ credentialType,
165
+ authTokenExpiresIn = '10080',
166
+ ...options
167
+ }
168
+ ) => {
169
+ if (disclosureType !== DisclosureType.NEW) {
170
+ return undefined;
171
+ }
172
+ const activationDate = computeActivationDate(options);
173
+
174
+ const newDisclosureRequest = {
175
+ offerMode: 'preloaded',
176
+ configurationType: 'issuing',
177
+ vendorEndpoint: 'integrated-issuing-identification',
178
+ types: [
179
+ {
180
+ type: credentialType,
181
+ },
182
+ ],
183
+ identityMatchers: {
184
+ rules: [
185
+ {
186
+ valueIndex: 0,
187
+ path: [credentialTypeToPick[credentialType]],
188
+ rule: 'pick',
189
+ },
190
+ ],
191
+ vendorUserIdIndex: 0,
192
+ },
193
+ setIssuingDefault: true,
194
+ duration: '1h', // 1 hour by default
195
+ vendorDisclosureId: Date.now(),
196
+ purpose: purpose ?? 'Issuing Career Credential', // by default have a generic message
197
+ activationDate: formatISO(activationDate),
198
+ authTokenExpiresIn: Number(authTokenExpiresIn),
199
+ termsUrl,
200
+ label,
201
+ };
202
+
203
+ return omitBy(isNil, newDisclosureRequest);
204
+ };
205
+
206
+ module.exports = {
207
+ prepareData,
208
+ };
@@ -0,0 +1,47 @@
1
+ const inquirer = require('inquirer');
2
+ const { format, parseISO } = require('date-fns');
3
+ const { take, map, flow } = require('lodash/fp');
4
+
5
+ const disclosureListQuestion = (disclosures) => ({
6
+ type: 'list',
7
+ name: 'disclosure',
8
+ message: 'Please select a disclosure',
9
+ choices: flow(
10
+ take(10),
11
+ map((disclosure) => ({
12
+ name: `${disclosure.purpose}, ${format(
13
+ parseISO(disclosure.createdAt),
14
+ 'MMM d yyyy h:mma'
15
+ )}`,
16
+ value: disclosure.id,
17
+ }))
18
+ )(disclosures),
19
+ });
20
+
21
+ const askQuestion = async (question) => {
22
+ const result = await inquirer.prompt([question]);
23
+ return result[question.name];
24
+ };
25
+
26
+ const askDisclosureType = () =>
27
+ askQuestion({
28
+ type: 'list',
29
+ name: 'disclosureType',
30
+ message:
31
+ 'Would you like to use an existing disclosure or create a new one?',
32
+ choices: ['existing', 'new'],
33
+ });
34
+ const askUseNewDisclosure = () =>
35
+ askQuestion({
36
+ type: 'confirm',
37
+ name: 'useNewDisclosure',
38
+ message: 'The batch will create a new disclosure. Press enter to confirm.',
39
+ });
40
+ const askDisclosureList = (disclosures) =>
41
+ askQuestion(disclosureListQuestion(disclosures));
42
+
43
+ module.exports = {
44
+ askDisclosureType,
45
+ askDisclosureList,
46
+ askUseNewDisclosure,
47
+ };
@@ -0,0 +1,11 @@
1
+ const { existsSync } = require('fs');
2
+
3
+ const validateDirectoryExists = ({ path }) => {
4
+ if (!existsSync(path)) {
5
+ throw new Error(`${path} does not exist`);
6
+ }
7
+ };
8
+
9
+ module.exports = {
10
+ validateDirectoryExists,
11
+ };
@@ -0,0 +1,14 @@
1
+ const { program } = require('commander');
2
+ const packageJson = require('../package.json');
3
+
4
+ program
5
+ .version(packageJson.version, '-v, --vers', 'Current tool version')
6
+ .command('vendorcreds', 'load data from csv to the vendor', {
7
+ executableFile: `${__dirname}/vendor-credentials/index.js`,
8
+ })
9
+ .command('batchissuing', 'issue credentials from csv', {
10
+ executableFile: `${__dirname}/batch-issuing/index.js`,
11
+ })
12
+ .usage('[command] [options]')
13
+ .passThroughOptions()
14
+ .parse(process.argv);
@@ -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,6 @@
1
+ module.exports = {
2
+ ...require('./compute-activation-date'),
3
+ ...require('./common'),
4
+ ...require('./load-csv'),
5
+ ...require('./load-handlebars-template'),
6
+ };