Skip to content

Commit

Permalink
Merge branch 'main' into cirrus-logs
Browse files Browse the repository at this point in the history
  • Loading branch information
codemist authored Jan 14, 2025
2 parents 1412e7c + 536da05 commit 173744c
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 9 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
"lint-staged": "^15.3.0",
"mjml-browser": "^4.15.3",
"prettier": "3.4.2",
"sass": "^1.83.0",
"sass": "^1.83.1",
"storybook": "^8.4.6",
"stylelint": "^16.12.0",
"stylelint-config-recommended-scss": "^14.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ const mockedSubscriber: SubscriberRow = {
monthly_monitor_report: false,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

const mockedUser: Session["user"] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const mockedSubscriber: SubscriberRow = {
monthly_monitor_report: false,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

const mockedUser: Session["user"] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export function up (knex) {
return knex.schema.table("subscribers", table => {
table.timestamp("churn_prevention_email_sent_at");
table.index("churn_prevention_email_sent_at");
});
}

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export function down (knex) {
return knex.schema.table("subscribers", table => {
table.dropIndex("churn_prevention_email_sent_at")
table.dropColumn("churn_prevention_email_sent_at");
});
}
41 changes: 41 additions & 0 deletions src/db/tables/subscribers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,33 @@ async function markFirstDataBrokerRemovalFixedEmailAsJustSent(
}

/* c8 ignore stop */

// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function markChurnPreventionEmailAsJustSent(
subscriberId: SubscriberRow["id"],
) {
const affectedSubscribers = await knex("subscribers")
.update({
// @ts-ignore knex.fn.now() results in it being set to a date,
// even if it's not typed as a JS date object:
churn_prevention_email_sent_at: knex.fn.now(),
// @ts-ignore knex.fn.now() results in it being set to a date,
// even if it's not typed as a JS date object:
updated_at: knex.fn.now(),
})
.where("id", subscriberId)
.returning("*");

if (affectedSubscribers.length !== 1) {
throw new Error(
`Attempted to mark 1 user as having just been sent the churn prevention email, but instead found [${affectedSubscribers.length}] matching its ID.`,
);
}
}

/* c8 ignore stop */

// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function markMonthlyActivityPlusEmailAsJustSent(
Expand Down Expand Up @@ -669,6 +696,18 @@ async function isSubscriberPlus(subscriberId: SubscriberRow["id"]) {
}
/* c8 ignore stop */

// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function getChurnPreventionEmailSentAt(
subscriberId: SubscriberRow["id"],
) {
const res = await knex("subscribers")
.select("churn_prevention_email_sent_at")
.where("id", subscriberId);
return res?.[0]?.["churn_prevention_email_sent_at"] ?? null;
}
/* c8 ignore stop */

export {
getOnerepProfileId,
getSubscribersByHashes,
Expand All @@ -685,6 +724,7 @@ export {
getPotentialSubscribersWaitingForFirstDataBrokerRemovalFixedEmail,
getFreeSubscribersWaitingForMonthlyEmail,
getPlusSubscribersWaitingForMonthlyEmail,
markChurnPreventionEmailAsJustSent,
markFirstDataBrokerRemovalFixedEmailAsJustSent,
markMonthlyActivityPlusEmailAsJustSent,
deleteUnverifiedSubscribers,
Expand All @@ -693,6 +733,7 @@ export {
deleteOnerepProfileId,
incrementSignInCountForEligibleFreeUser,
getSignInCount,
getChurnPreventionEmailSentAt,
unresolveAllBreaches,
isSubscriberPlus,
knex as knexSubscribers,
Expand Down
2 changes: 2 additions & 0 deletions src/knex-tables.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ declare module "knex/types/tables" {
sign_in_count: null | number;
email_addresses: SubscriberEmail[];
first_broker_removal_email_sent: boolean;
churn_prevention_email_sent_at: null | Date;
}
type SubscriberOptionalColumns = Extract<
keyof SubscriberRow,
Expand All @@ -174,6 +175,7 @@ declare module "knex/types/tables" {
| "onerep_profile_id"
| "email_addresses"
| "first_broker_removal_email_sent"
| "churn_prevention_email_sent_at"
>;
type SubscriberAutoInsertedColumns = Extract<
keyof SubscriberRow,
Expand Down
2 changes: 1 addition & 1 deletion src/scripts/build/uploadAutoCompleteLocations.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import os from "os";
import path from "path";
import fs from "fs";
import AdmZip from "adm-zip";
import { uploadToS3 } from "../../utils/s3.js";

const REMOTE_DATA_URL = "https://download.geonames.org/export/dump";
const DATA_COUNTRY_CODE = "US";
Expand Down Expand Up @@ -328,7 +329,6 @@ try {
if (process.argv.includes("--skip-upload")) {
console.debug("Skipping S3 upload");
} else {
const uploadToS3 = await import("../s3.js");
await uploadToS3(`autocomplete/${LOCATIONS_DATA_FILE}`, readStream);
}

Expand Down
45 changes: 45 additions & 0 deletions src/utils/fxa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,50 @@ async function deleteSubscription(bearerToken: string): Promise<boolean> {
}
/* c8 ignore stop */

// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function reactivate(bearerToken: string): Promise<void> {
try {
const subs = (await getSubscriptions(bearerToken)) ?? [];
let subscriptionId;
for (const sub of subs) {
if (
sub &&
sub.productId &&
sub.productId === process.env.PREMIUM_PRODUCT_ID
) {
subscriptionId = sub.subscriptionId;
}
}
if (subscriptionId) {
const reactivateSubscriptionUrl = `${envVars.OAUTH_ACCOUNT_URI}/oauth/subscriptions/reactivate`;
const response = await fetch(reactivateSubscriptionUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${bearerToken}`,
},
body: JSON.stringify({
subscriptionId,
}),
});
const responseJson = await response.json();
if (!response.ok) throw new Error(responseJson);
logger.info("reactivate_fxa_subscription_success");
}
} catch (e) {
if (e instanceof Error) {
logger.error("reactivate_fxa_subscription", {
stack: e.stack,
message: e.message,
});
}
throw e;
}
}
/* c8 ignore stop */

// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function applyCoupon(
Expand Down Expand Up @@ -418,6 +462,7 @@ export {
getSubscriptions,
getBillingAndSubscriptions,
deleteSubscription,
reactivate,
applyCoupon,
getAttachedClients,
};
10 changes: 7 additions & 3 deletions src/utils/s3.ts → src/utils/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ const s3 = new S3({
},
});

export async function uploadToS3(fileName: string, fileStream: Buffer) {
/**
* @param {string} fileName
* @param {Buffer} fileStream
*/
export async function uploadToS3(fileName, fileStream) {
console.log("Attempt to upload to s3: ", fileName);
const uploadParams = {
Bucket,
Expand All @@ -37,7 +41,7 @@ export async function uploadToS3(fileName: string, fileStream: Buffer) {
params: uploadParams,
}).done();
console.log("Successfully uploaded data to " + Bucket + "/" + fileName);
} catch (err) {
console.error(err, (err as Error).stack);
} catch (/** @type {any} */ err) {
console.error(err, err.stack);
}
}
7 changes: 7 additions & 0 deletions src/utils/subscriberBreaches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const subscriber: SubscriberRow = {
monthly_monitor_report: false,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

const allBreaches: HibpLikeDbBreach[] = [
Expand Down Expand Up @@ -550,6 +551,7 @@ describe("getSubBreaches", () => {
onerep_profile_id: null,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

(
Expand Down Expand Up @@ -649,6 +651,7 @@ describe("getSubBreaches", () => {
onerep_profile_id: null,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

(
Expand Down Expand Up @@ -756,6 +759,7 @@ describe("getSubBreaches", () => {
onerep_profile_id: null,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

(
Expand Down Expand Up @@ -873,6 +877,7 @@ describe("getSubBreaches", () => {
onerep_profile_id: null,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

(
Expand Down Expand Up @@ -990,6 +995,7 @@ describe("getSubBreaches", () => {
onerep_profile_id: null,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

(
Expand Down Expand Up @@ -1103,6 +1109,7 @@ describe("getSubBreaches", () => {
onerep_profile_id: null,
sign_in_count: null,
first_broker_removal_email_sent: false,
churn_prevention_email_sent_at: null,
};

(
Expand Down

0 comments on commit 173744c

Please sign in to comment.