Skip to content

Commit

Permalink
refactor: move the unsubscribe endpoint to notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
idoshamun committed May 28, 2024
1 parent 9c9534a commit 4e529ab
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 97 deletions.
40 changes: 40 additions & 0 deletions __tests__/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
NotificationPreferenceSource,
NotificationPreference,
NotificationAttachmentType,
UserPersonalizedDigest,
} from '../src/entity';
import { DataSource } from 'typeorm';
import createOrGetConnection from '../src/db';
Expand All @@ -42,6 +43,8 @@ import {
NotificationAttachmentV2,
NotificationAvatarV2,
} from '../src/entity';
import { signJwt } from '../src/auth';
import { UnsubscribeGroup } from '../src/common';

let app: FastifyInstance;
let con: DataSource;
Expand Down Expand Up @@ -1227,3 +1230,40 @@ describe('mutation subscribeNotificationPreference', () => {
);
});
});

describe('POST /notifications/unsubscribe', () => {
it('should unsubscribe from notifications', async () => {
await con
.getRepository(User)
.update({ id: '1' }, { notificationEmail: true });
const token = await signJwt({
userId: '1',
group: UnsubscribeGroup.Notifications,
});
await request(app.server)
.post('/notifications/unsubscribe')
.query({ token: token.token })
.expect(204);
const user = await con.getRepository(User).findOneBy({ id: '1' });
expect(user.notificationEmail).toBe(false);
});

it('should unsubscribe from digest', async () => {
const upd1 = await con
.getRepository(UserPersonalizedDigest)
.findBy({ userId: '1' });
expect(upd1.length).toBe(1);
const token = await signJwt({
userId: '1',
group: UnsubscribeGroup.Digest,
});
await request(app.server)
.post('/notifications/unsubscribe')
.query({ token: token.token })
.expect(204);
const upd2 = await con
.getRepository(UserPersonalizedDigest)
.findBy({ userId: '1' });
expect(upd2.length).toBe(0);
});
});
62 changes: 0 additions & 62 deletions __tests__/routes.ts

This file was deleted.

37 changes: 2 additions & 35 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,11 @@ import localAds from './localAds';
import automations from './automations';
import sitemaps from './sitemaps';
import createOrGetConnection from '../db';
import {
User,
UserPersonalizedDigest,
UserPersonalizedDigestType,
} from '../entity';
import { notifyGeneratePersonalizedDigest, UnsubscribeGroup } from '../common';
import { UserPersonalizedDigest, UserPersonalizedDigestType } from '../entity';
import { notifyGeneratePersonalizedDigest } from '../common';
import { PersonalizedDigestFeatureConfig } from '../growthbook';
import privateRpc from './privateRpc';
import { connectRpcPlugin } from '../common/connectRpc';
import { verifyJwt } from '../auth';

export default async function (fastify: FastifyInstance): Promise<void> {
fastify.register(rss, { prefix: '/rss' });
Expand Down Expand Up @@ -136,32 +131,4 @@ Disallow: /`);
message: 'ok',
});
});

// https://customer.io/docs/journeys/custom-unsubscribe-links/#step-1-configure-your-system-to-receive-the-post-when-recipients-click-unsubscribe
fastify.post<{ Querystring: { token: string } }>(
'/unsubscribe',
async (req, res) => {
const payload = await verifyJwt<{
userId: string;
group: UnsubscribeGroup;
}>(req.query.token);
if (payload) {
const con = await createOrGetConnection();
switch (payload.group) {
case UnsubscribeGroup.Notifications:
await con
.getRepository(User)
.update({ id: payload.userId }, { notificationEmail: false });
break;
case UnsubscribeGroup.Digest:
await con.getRepository(UserPersonalizedDigest).delete({
userId: payload.userId,
type: UserPersonalizedDigestType.Digest,
});
break;
}
}
return res.status(204).send();
},
);
}
36 changes: 36 additions & 0 deletions src/routes/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { FastifyInstance } from 'fastify';
import { injectGraphql } from '../compatibility/utils';
import { verifyJwt } from '../auth';
import { UnsubscribeGroup } from '../common';
import createOrGetConnection from '../db';
import {
User,
UserPersonalizedDigest,
UserPersonalizedDigestType,
} from '../entity';

export default async function (fastify: FastifyInstance): Promise<void> {
fastify.get('/', async (req, res) => {
Expand All @@ -9,4 +17,32 @@ export default async function (fastify: FastifyInstance): Promise<void> {

return injectGraphql(fastify, { query }, (obj) => obj['data'], req, res);
});

// https://customer.io/docs/journeys/custom-unsubscribe-links/#step-1-configure-your-system-to-receive-the-post-when-recipients-click-unsubscribe
fastify.post<{ Querystring: { token: string } }>(
'/unsubscribe',
async (req, res) => {
const payload = await verifyJwt<{
userId: string;
group: UnsubscribeGroup;
}>(req.query.token);
if (payload) {
const con = await createOrGetConnection();
switch (payload.group) {
case UnsubscribeGroup.Notifications:
await con
.getRepository(User)
.update({ id: payload.userId }, { notificationEmail: false });
break;
case UnsubscribeGroup.Digest:
await con.getRepository(UserPersonalizedDigest).delete({
userId: payload.userId,
type: UserPersonalizedDigestType.Digest,
});
break;
}
}
return res.status(204).send();
},

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
);
}

0 comments on commit 4e529ab

Please sign in to comment.