{"version":3,"file":"bulkRestartRequests-BOmqsdP0.mjs","names":[],"sources":["../src/lib/requests/restartPrivacyRequest.ts","../src/lib/requests/bulkRestartRequests.ts"],"sourcesContent":["import { IdentifierType, RestartIdentifierStrategy } from '@transcend-io/privacy-types';\nimport type { RequestIdentifier } from '@transcend-io/sdk';\nimport { apply, decodeCodec } from '@transcend-io/type-utils';\nimport type { Got } from 'got';\nimport * as t from 'io-ts';\nimport { groupBy } from 'lodash-es';\n\nimport type { PrivacyRequest } from '../graphql/index.js';\nimport { IDENTIFIER_BLOCK_LIST } from './constants.js';\nimport { PrivacyRequestResponse } from './submitPrivacyRequest.js';\n\n/**\n * Restart a privacy request to the Transcend API\n *\n * @param sombra - Sombra instance configured to make requests\n * @param request - Request to restart\n * @param input - Request input\n * @returns Successfully submitted request\n */\nexport async function restartPrivacyRequest(\n  sombra: Got,\n  request: PrivacyRequest,\n  {\n    sendEmailReceipt = false,\n    skipWaitingPeriod = false,\n    emailIsVerified = true,\n    requestIdentifiers = [],\n    restartIdentifierStrategy,\n  }: {\n    /** List of request identifiers to include */\n    requestIdentifiers?: RequestIdentifier[];\n    /** When true, send an email receipt to data subject */\n    sendEmailReceipt?: boolean;\n    /** Whether the email is verified */\n    emailIsVerified?: boolean;\n    /** Whether to skip waiting period */\n    skipWaitingPeriod?: boolean;\n    /** How request identifiers should be handled when restarting */\n    restartIdentifierStrategy?: RestartIdentifierStrategy;\n  } = {},\n): Promise<PrivacyRequestResponse> {\n  // Make the GraphQL request\n  const response = await sombra\n    .post('v1/data-subject-request', {\n      json: {\n        type: request.type,\n        subject: {\n          coreIdentifier: request.coreIdentifier,\n          email: request.email,\n          emailIsVerified,\n          ...(requestIdentifiers.length > 0\n            ? {\n                attestedExtraIdentifiers: apply(\n                  groupBy(\n                    requestIdentifiers\n                      .filter(\n                        (ri) =>\n                          // these are already submitted above\n                          !(ri.name === 'email' && ri.value === request.email) &&\n                          !IDENTIFIER_BLOCK_LIST.includes(ri.name),\n                      )\n                      .map((ri) => ({\n                        ...ri,\n                        type: Object.values(IdentifierType).includes(\n                          ri.name as any, // eslint-disable-line @typescript-eslint/no-explicit-any\n                        )\n                          ? ri.name\n                          : IdentifierType.Custom,\n                      })),\n                    'type',\n                  ),\n                  (values, type) =>\n                    values.map(({ name, value }) => ({\n                      ...(type === IdentifierType.Custom ? { name } : {}),\n                      value,\n                    })),\n                ),\n              }\n            : {}),\n        },\n        requestId: request.id,\n        subjectType: request.subjectType,\n        isSilent: request.isSilent,\n        isTest: request.isTest,\n        locale: request.locale,\n        skipWaitingPeriod,\n        createdAt: request.createdAt,\n        details: `Restarted by Transcend cli: \"tr-request-restart\" - ${request.details}`,\n        skipSendingReceipt: !sendEmailReceipt,\n        ...(restartIdentifierStrategy ? { restartIdentifierStrategy } : {}),\n      },\n    })\n    .json();\n\n  const { request: requestResponse } = decodeCodec(\n    t.type({\n      request: PrivacyRequestResponse,\n    }),\n    response,\n  );\n  return requestResponse;\n}\n","import { join, resolve } from 'node:path';\n\nimport { PersistedState } from '@transcend-io/persisted-state';\nimport {\n  RequestAction,\n  RequestStatus,\n  RestartIdentifierStrategy,\n} from '@transcend-io/privacy-types';\nimport {\n  buildTranscendGraphQLClient,\n  createSombraGotInstance,\n  fetchAllRequestIdentifiers,\n  validateSombraVersion,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\nimport * as t from 'io-ts';\nimport { difference } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\nimport { SuccessfulRequest } from './constants.js';\nimport { extractClientError } from './extractClientError.js';\nimport { restartPrivacyRequest } from './restartPrivacyRequest.js';\n\n/** Minimal state we need to keep a list of requests */\nconst ErrorRequest = t.intersection([\n  SuccessfulRequest,\n  t.type({\n    error: t.string,\n  }),\n]);\n\n/** Type override */\ntype ErrorRequest = t.TypeOf<typeof ErrorRequest>;\n\n/** Persist this data between runs of the script */\nconst CachedRequestState = t.type({\n  restartedRequests: t.array(SuccessfulRequest),\n  failingRequests: t.array(ErrorRequest),\n});\n\n/**\n * Upload a set of privacy requests from CSV\n *\n * @param options - Options\n */\nexport async function bulkRestartRequests({\n  requestReceiptFolder,\n  auth,\n  sombraAuth,\n  requestActions,\n  requestStatuses,\n  createdAtBefore,\n  createdAtAfter,\n  updatedAtBefore,\n  updatedAtAfter,\n  transcendUrl = DEFAULT_TRANSCEND_API,\n  requestIds = [],\n  createdAt = new Date(),\n  silentModeBefore,\n  sendEmailReceipt = false,\n  emailIsVerified = true,\n  copyIdentifiers = false,\n  restartIdentifierStrategy,\n  skipWaitingPeriod = false,\n  concurrency = 20,\n}: {\n  /** Actions to filter for */\n  requestActions: RequestAction[];\n  /** Statues to filter for */\n  requestStatuses: RequestStatus[];\n  /** File where request receipts are stored */\n  requestReceiptFolder: string;\n  /** Transcend API key authentication */\n  auth: string;\n  /** API URL for Transcend backend */\n  transcendUrl?: string;\n  /** Sombra API key authentication */\n  sombraAuth?: string;\n  /** Request IDs to filter for */\n  requestIds?: string[];\n  /** Whether to re-verify the email when restarting the request */\n  emailIsVerified?: boolean;\n  /** Filter for requests that were submitted before this date */\n  createdAt?: Date;\n  /** Requests that have been open for this length of time should be marked as silent mode */\n  silentModeBefore?: Date;\n  /** Send an email receipt to the restarted requests */\n  sendEmailReceipt?: boolean;\n  /** Copy over all identifiers rather than restarting the request only with the core identifier */\n  copyIdentifiers?: boolean;\n  /** How request identifiers should be handled when restarting */\n  restartIdentifierStrategy?: RestartIdentifierStrategy;\n  /** Skip the waiting period when restarting requests */\n  skipWaitingPeriod?: boolean;\n  /** Filter for requests created before this date */\n  createdAtBefore?: Date;\n  /** Filter for requests created after this date */\n  createdAtAfter?: Date;\n  /** Filter for requests updated before this date */\n  updatedAtBefore?: Date;\n  /** Filter for requests updated after this date */\n  updatedAtAfter?: Date;\n  /** Concurrency to upload requests at */\n  concurrency?: number;\n}): Promise<void> {\n  // Time duration\n  const t0 = new Date().getTime();\n  // create a new progress bar instance and use shades_classic theme\n  const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n  // Create a new state file to store the requests from this run.\n  // `toISOString()` contains colons (e.g. 2026-07-06T04:33:12.345Z) which are\n  // illegal characters in Windows filenames, so strip them out to keep the\n  // auto-generated receipt filename cross-platform.\n  const cacheFile = join(\n    requestReceiptFolder,\n    `tr-request-restart-${new Date().toISOString().replace(/:/g, '-')}.json`,\n  );\n  const state = new PersistedState(cacheFile, CachedRequestState, {\n    restartedRequests: [],\n    failingRequests: [],\n  });\n\n  // Create sombra instance to communicate with\n  const sombra = await createSombraGotInstance(transcendUrl, auth, {\n    logger,\n    sombraApiKey: sombraAuth,\n    sombraUrl: process.env.SOMBRA_URL,\n  });\n\n  // Find all requests made before createdAt that are in a removing data state\n  const client = buildTranscendGraphQLClient(transcendUrl, auth);\n  const allRequests = await fetchAllRequests(client, {\n    requestIds,\n    actions: requestActions,\n    statuses: requestStatuses,\n    createdAtBefore,\n    createdAtAfter,\n    updatedAtBefore,\n    updatedAtAfter,\n  });\n  const requests = allRequests.filter((request) => new Date(request.createdAt) < createdAt);\n  logger.info(`Found ${requests.length} requests to restart`);\n\n  if (copyIdentifiers) {\n    logger.info('copyIdentifiers detected - All Identifiers will be copied.');\n  }\n  if (restartIdentifierStrategy) {\n    logger.info(\n      `restartIdentifierStrategy detected - Using \"${restartIdentifierStrategy}\" strategy.`,\n    );\n  }\n  if (sendEmailReceipt) {\n    logger.info('sendEmailReceipt detected - Email receipts will be sent.');\n  }\n  if (skipWaitingPeriod) {\n    logger.info('skipWaitingPeriod detected - Waiting period will be skipped.');\n  }\n\n  // Validate request IDs\n  if (requestIds.length > 0 && requestIds.length !== requests.length) {\n    const missingRequests = difference(\n      requestIds,\n      requests.map(({ id }) => id),\n    );\n    if (missingRequests.length > 0) {\n      logger.error(\n        colors.red(`Failed to find the following requests by ID: ${missingRequests.join(',')}.`),\n      );\n      process.exit(1);\n    }\n  }\n\n  if (copyIdentifiers) {\n    await validateSombraVersion(client, { logger });\n  }\n\n  // Map over the requests\n  let total = 0;\n  progressBar.start(requests.length, 0);\n  await map(\n    requests,\n    async (request, ind) => {\n      try {\n        // Pull the request identifiers\n        const requestIdentifiers = copyIdentifiers\n          ? await fetchAllRequestIdentifiers(client, sombra, {\n              filterBy: { requestId: request.id },\n              skipSombraCheck: true,\n              logger,\n            })\n          : [];\n\n        // Make the GraphQL request to restart the request\n        const requestResponse = await restartPrivacyRequest(\n          sombra,\n          {\n            ...request,\n            // override silent mode\n            isSilent:\n              !!silentModeBefore && new Date(request.createdAt) < silentModeBefore\n                ? true\n                : request.isSilent,\n          },\n          {\n            requestIdentifiers,\n            skipWaitingPeriod,\n            sendEmailReceipt,\n            emailIsVerified,\n            restartIdentifierStrategy,\n          },\n        );\n\n        // Cache successful upload\n        const restartedRequests = state.getValue('restartedRequests');\n        restartedRequests.push({\n          id: requestResponse.id,\n          link: requestResponse.link,\n          rowIndex: ind,\n          coreIdentifier: requestResponse.coreIdentifier,\n          attemptedAt: new Date().toISOString(),\n        });\n        await state.setValue(restartedRequests, 'restartedRequests');\n      } catch (err) {\n        const msg = `${err.message} - ${JSON.stringify(err.response?.body, null, 2)}`;\n        const clientError = extractClientError(msg);\n\n        const failingRequests = state.getValue('failingRequests');\n        failingRequests.push({\n          id: request.id,\n          link: request.link,\n          rowIndex: ind,\n          coreIdentifier: request.coreIdentifier,\n          attemptedAt: new Date().toISOString(),\n          error: clientError || msg,\n        });\n        await state.setValue(failingRequests, 'failingRequests');\n      }\n      total += 1;\n      progressBar.update(total);\n    },\n    { concurrency },\n  );\n\n  progressBar.stop();\n  const t1 = new Date().getTime();\n  const totalTime = t1 - t0;\n\n  // Log completion time\n  logger.info(colors.green(`Completed restarting of requests in \"${totalTime / 1000}\" seconds.`));\n\n  // Log errors\n  if (state.getValue('failingRequests').length > 0) {\n    logger.error(\n      colors.red(\n        `Encountered \"${state.getValue('failingRequests').length}\" errors. ` +\n          `See \"${resolve(cacheFile)}\" to review the error messages and inputs.`,\n      ),\n    );\n    process.exit(1);\n  }\n}\n"],"mappings":"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"}