Skip to content

Commit be77182

Browse files
committed
Continue to post fields when some return 403
Changemakers may revoke (or simply not grant) permission to CN. This is an expected situation, so continue when a posted field value returns HTTP 403 Forbidden status. Because it is easy to lose track of the `WARN` lines in output, collect the failed changemaker IDs and log a final warning displaying all the changemakers that returned 403. Issue #280 Support partial success on 403s in CN script
1 parent 6a0ad29 commit be77182

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

src/charityNavigator.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { writeFile } from 'node:fs/promises';
22
import { ApolloClient, InMemoryCache, type TypedDocumentNode, gql } from '@apollo/client';
33
import { SetContextLink } from '@apollo/client/link/context';
44
import { HttpLink } from '@apollo/client/link/http';
5+
import { AxiosError } from 'axios';
56
import { isValidEin } from './ein.js';
67
import { logger } from './logger.js';
78
import { type AccessTokenSet, getToken, oidcOptions } from './oidc.js';
@@ -11,6 +12,7 @@ import {
1112
postChangemakerFieldValue,
1213
postChangemakerFieldValueBatch,
1314
postSource,
15+
type WritableChangemakerFieldValue,
1416
} from './pdc-api.js';
1517
import type { CommandModule } from 'yargs';
1618
import type { Changemaker, ChangemakerBundle, Source } from '@pdc/sdk';
@@ -20,6 +22,8 @@ const JSON_SPACES = 2;
2022
// Fixed page size for Charity Navigator GraphQL requests; a positive
2123
// constant keeps perPage valid when the EIN list is empty (GLM-5.2).
2224
const PER_PAGE = 100;
25+
// When `@pdc/http-status-codes` is ready (issues 18-20 solved), use it instead.
26+
const HTTP_STATUS_FORBIDDEN = 403;
2327

2428
interface NonprofitPublic {
2529
ein: string;
@@ -288,6 +292,26 @@ const getChangemakerByEin = (ein: string, changemakers: ChangemakerBundle): Chan
288292
throw new Error('How could this have happened?');
289293
};
290294

295+
/** Light wrapper around `postChangemakerFieldValue` that logs warning on HTTP 403 */
296+
const postChangemakerFieldValueWarnOnForbidden = async (
297+
baseUrl: string,
298+
token: AccessTokenSet,
299+
data: WritableChangemakerFieldValue,
300+
warnedChangemakers: Set<number>, // Mutated! This is for observation/logs, not control!
301+
): Promise<void> => {
302+
try {
303+
const fieldValue = await postChangemakerFieldValue(baseUrl, token, data);
304+
logger.info(`Added changemaker field value: ${JSON.stringify(fieldValue)}`);
305+
} catch (e: unknown) {
306+
if (e instanceof AxiosError && e.status === HTTP_STATUS_FORBIDDEN) {
307+
logger.warn(`No permission (403) to create ${JSON.stringify(data)}`);
308+
warnedChangemakers.add(data.changemakerId);
309+
} else {
310+
throw e;
311+
}
312+
}
313+
};
314+
291315
const lookupFromPdcCommand: CommandModule<unknown, LookupFromPdcCommandArgs> = {
292316
command: 'lookupFromPdc',
293317
describe: 'Fetch and display information about organizations present in PDC',
@@ -425,6 +449,7 @@ const updateAllCommand: CommandModule<unknown, UpdateAllCommandArgs> = {
425449
sourceId: source.id,
426450
notes: `data-scripts charityNavigator.ts execution ${Date.now()}`,
427451
});
452+
const missingPermissionChangemakerIds: Set<number> = new Set<number>();
428453
// Last, for each nonprofit, for each field, post the field. These are
429454
// issued sequentially rather than via Promise.all because the PDC API
430455
// times out under concurrent POSTs to /changemakerFieldValues.
@@ -438,19 +463,29 @@ const updateAllCommand: CommandModule<unknown, UpdateAllCommandArgs> = {
438463
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition --
439464
The cnAttribute can really be null even though types say otherwise. */
440465
if (cnAttribute !== undefined && cnAttribute !== null) {
441-
const fieldValue = await postChangemakerFieldValue(args.pdcApiBaseUrl, token, {
466+
const fieldValue = {
442467
changemakerId: changemaker.id,
443468
batchId: fieldBatch.id,
444469
baseFieldShortCode,
445470
value: cnAttribute.toString(),
446471
goodAsOf: e.updatedAt,
447-
});
448-
logger.info(`Added changemaker field value: ${JSON.stringify(fieldValue)}`);
472+
};
473+
await postChangemakerFieldValueWarnOnForbidden(
474+
args.pdcApiBaseUrl,
475+
token,
476+
fieldValue,
477+
missingPermissionChangemakerIds,
478+
);
449479
}
450480
}
451481
}
452482
}
453483
/* eslint-enable no-await-in-loop */
484+
if (missingPermissionChangemakerIds.size > 0) {
485+
logger.warn(
486+
`No permission for at least one field in each of these changemakers (so not updated): ${JSON.stringify([...missingPermissionChangemakerIds])}`,
487+
);
488+
}
454489
},
455490
};
456491

src/pdc-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ const postPlatformProviderData = async (
133133
export {
134134
type ChangemakerFieldValue,
135135
type ChangemakerFieldValueBatch,
136+
type WritableChangemakerFieldValue,
136137
getBaseFields,
137138
getChangemakers,
138139
getProposals,

0 commit comments

Comments
 (0)