Skip to content

Commit

Permalink
email digest
Browse files Browse the repository at this point in the history
  • Loading branch information
k2xl committed Dec 31, 2024
1 parent ad64a92 commit f939dfb
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 2 deletions.
1 change: 1 addition & 0 deletions models/db/user.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface User {
utm_source?: string; // how the user found the site
// virtual field - not stored in schema
config?: UserConfig;
configs?: UserConfig[];
}

export interface ReqUser extends User {
Expand Down
17 changes: 15 additions & 2 deletions pages/api/internal-jobs/email-digest/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as aws from '@aws-sdk/client-ses';
import { defaultProvider } from '@aws-sdk/credential-provider-node';
import { getStreakRankIndex, STREAK_RANK_GROUPS } from '@root/components/counters/AnimateCounterOne';
import DiscordChannel from '@root/constants/discordChannel';
import { GameId } from '@root/constants/GameId';
import { Games } from '@root/constants/Games';
Expand All @@ -10,6 +11,7 @@ import { getEnrichUserConfigPipelineStage } from '@root/helpers/enrich';
import { getGameFromId } from '@root/helpers/getGameIdFromReq';
import EmailLog from '@root/models/db/emailLog';
import { EnrichedLevel } from '@root/models/db/level';
import UserConfig from '@root/models/db/userConfig';
import { convert } from 'html-to-text';
import { Types } from 'mongoose';
import { NextApiRequest, NextApiResponse } from 'next';
Expand Down Expand Up @@ -169,7 +171,7 @@ export async function sendEmailDigests(batchId: Types.ObjectId, limit: number) {
from: UserConfigModel.collection.name,
localField: '_id',
foreignField: 'userId',
as: 'userConfig',
as: 'configs',
},
},
// join email logs and get the last one
Expand Down Expand Up @@ -328,11 +330,22 @@ export async function sendEmailDigests(batchId: Types.ObjectId, limit: number) {
NewUserEmail = NewUserCampaign[daysRegistered];
}

let dailySubject = 'Levels of the Day - ' + todaysDatePretty;

// get max config.calcCurrentStreak for all configs
const maxStreak = user.configs?.reduce((max, config) => Math.max(max, config.calcCurrentStreak || 0), 0);

if (maxStreak && maxStreak > 0) {
const streakRank = STREAK_RANK_GROUPS[getStreakRankIndex(maxStreak)];

dailySubject = `Keep your streak of ${maxStreak} day${maxStreak === 1 ? '' : 's'} going! ${streakRank.emoji}`;
}

/* istanbul ignore next */
const EmailTextTable: { [key: string]: { title: string, message?: string, subject: string, featuredLevelsLabel: string } } = {
[EmailType.EMAIL_DIGEST]: {
title: `Welcome to your Thinky.gg daily digest for ${todaysDatePretty}.`,
subject: 'Levels of the Day - ' + todaysDatePretty,
subject: dailySubject,
featuredLevelsLabel: 'Levels of the Day',
},
[EmailType.EMAIL_7D_REACTIVATE]: {
Expand Down
79 changes: 79 additions & 0 deletions tests/pages/api/internal-jobs/email-digest.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DEFAULT_GAME_ID } from '@root/constants/GameId';
import { NextApiRequestWrapper } from '@root/helpers/apiWrapper';
import { enableFetchMocks } from 'jest-fetch-mock';
import MockDate from 'mockdate';
import { testApiHandler } from 'next-test-api-route-handler';
import { Logger } from 'winston';
import { EmailDigestSettingType, EmailType } from '../../../../constants/emailDigest';
Expand Down Expand Up @@ -314,6 +315,84 @@ describe('Email digest', () => {
},
});
}, 10000);

test('Run it with a user who has a streak', async () => {
// setup
await dbConnect();
sendMailRefMock.ref = acceptMock;
jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger));
jest.spyOn(logger, 'info').mockImplementation(() => ({} as Logger));
jest.spyOn(logger, 'warn').mockImplementation(() => ({} as Logger));

const tomorrow = Date.now() + (1000 * 60 * 60 * 24 ); // Note... Date.now() here is being mocked each time too!

MockDate.set(tomorrow);

await Promise.all([
UserModel.findByIdAndUpdate(TestId.USER, {
ts: Math.floor(Date.now() / 1000) - (30 * 24 * 60 * 60) // Set registration date to 30 days ago so get by all the intro emails
}),
EmailLogModel.deleteMany({ type: EmailType.EMAIL_DIGEST, userId: TestId.USER }),
UserConfigModel.findOneAndUpdate(
{ userId: TestId.USER },
{
calcCurrentStreak: 7,
lastPlayedAt: new Date(),
emailDigest: EmailDigestSettingType.DAILY
},
{ upsert: true }
)
]);

// Update user config to have a streak

await testApiHandler({
pagesHandler: async (_, res) => {
const req: NextApiRequestWrapper = {
gameId: DEFAULT_GAME_ID,
method: 'GET',
query: {
secret: process.env.INTERNAL_JOB_TOKEN_SECRET_EMAILDIGEST
},
body: {},
headers: {
'content-type': 'application/json',
},
} as unknown as NextApiRequestWrapper;

await handler(req, res);
},
test: async ({ fetch }) => {
const res = await fetch();
const response = await res.json();

console.log('done');
expect(response.error).toBeUndefined();
expect(res.status).toBe(200);

// Check the email logs to verify the subject line includes the streak
const emailLogs = await EmailLogModel.find({ userId: TestId.USER }, {}, { sort: { createdAt: -1 } });
const latestEmail = emailLogs[0];

expect(latestEmail).toBeDefined();
expect(latestEmail.subject).toContain('Keep your streak of 7 days going!');
expect(latestEmail.state).toBe(EmailState.SENT);

expect(response.sent[EmailType.EMAIL_DIGEST]).toHaveLength(4);

expect(response.failed[EmailType.EMAIL_DIGEST]).toHaveLength(0);
},
});

// Clean up - reset the streak
await UserConfigModel.findOneAndUpdate(
{ userId: TestId.USER },
{
calcCurrentStreak: 0,
lastPlayedAt: null
}
);
}, 10000);
test('Running with a user with no userconfig', async () => {
// delete user config
await Promise.all([UserModel.findByIdAndDelete(TestId.USER),
Expand Down

0 comments on commit f939dfb

Please sign in to comment.