export async function PUT()

in src/app/api/v1/user/breaches/route.ts [23:139]


export async function PUT(req: NextRequest) {
  const token = await getToken({ req });
  if (typeof token?.subscriber?.fxa_uid === "string") {
    try {
      const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
      if (!subscriber) {
        throw new Error("No subscriber found for current session.");
      }
      const allBreaches = await getBreaches();
      const j = await req.json();
      const {
        affectedEmail,
        breachId,
        resolutionsChecked,
      }: BreachResolutionRequest = j;
      const breachIdNumber = Number(breachId);
      const affectedEmailAsSubscriber =
        subscriber.primary_email === affectedEmail
          ? subscriber.primary_email
          : false;
      const affectedEmailInEmailAddresses =
        subscriber.email_addresses.find((ea) => ea.email === affectedEmail)
          ?.email || false;

      // check if current user's emails array contain affectedEmail
      if (!affectedEmailAsSubscriber && !affectedEmailInEmailAddresses) {
        return NextResponse.json({
          success: false,
          message: "Error: affectedEmail is not valid for this subscriber",
        });
      }

      // check if breach id is a part of affectEmail's breaches
      const { verifiedEmails } = await getAllEmailsAndBreaches(
        subscriber,
        allBreaches,
      );
      let currentEmail;
      if (affectedEmailAsSubscriber) {
        currentEmail = verifiedEmails.find(
          (ve) => ve.email === affectedEmailAsSubscriber,
        );
      } else {
        currentEmail = verifiedEmails.find(
          (ve) => ve.email === affectedEmailInEmailAddresses,
        );
      }
      const currentBreaches = currentEmail?.breaches?.filter(
        (b) => b.Id === breachIdNumber,
      );
      if (!currentBreaches) {
        return NextResponse.json({
          success: false,
          message: "Error: breachId provided does not exist",
        });
      }

      // check if resolutionsChecked array is a subset of the breaches' datatypes
      const isSubset = resolutionsChecked.every((val) =>
        currentBreaches[0].DataClasses.includes(val),
      );
      if (!isSubset) {
        return NextResponse.json({
          success: false,
          message: `Error: the resolutionChecked param contains more than allowed data types: [${resolutionsChecked.join(
            ", ",
          )}]`,
        });
      }

      // /* new JsonB:
      // {
      //   email_id: {
      //     recency_index: {
      //       resolutions: ['email', ...],
      //     }
      //   }
      // }
      // */

      // Typed as `any` because `subscriber` used to be typed as `any`, and
      // making that type more specific was enough work just by itself:
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const currentBreachResolution: any = subscriber.breach_resolution || {}; // get this from existing breach resolution if available
      currentBreachResolution[affectedEmail] = {
        ...(currentBreachResolution[affectedEmail] || {}),
        ...{
          [breachIdNumber]: {
            resolutionsChecked,
          },
        },
      };

      // set useBreachId to mark latest version of breach resolution
      // without this line, the get call might assume recency index
      currentBreachResolution.useBreachId = true;

      const updatedSubscriber = await setBreachResolution(
        subscriber,
        currentBreachResolution,
      );
      if (!updatedSubscriber) {
        throw new Error("Could not retrieve updated subscriber data.");
      }

      return NextResponse.json({
        success: true,
        breachResolutions: updatedSubscriber.breach_resolution,
      });
    } catch (e) {
      logger.error(e);
      return NextResponse.json({ success: false }, { status: 500 });
    }
  } else {
    return NextResponse.json({ success: false }, { status: 401 });
  }
}