Skip to content

Commit

Permalink
WIP Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
adamfraser committed Aug 21, 2024
1 parent 12f6871 commit a11b478
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 59 deletions.
3 changes: 3 additions & 0 deletions indexer/packages/notifications/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Service Level Variables

SERVICE_NAME=notifications
8 changes: 8 additions & 0 deletions indexer/packages/notifications/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DB_NAME=dydx_dev
DB_USERNAME=dydx_dev
DB_PASSWORD=dydxserver123
PG_POOL_MAX=2
PG_POOL_MIN=1
DB_HOSTNAME=postgres
DB_READONLY_HOSTNAME=postgres
DB_PORT=5432
Empty file.
10 changes: 10 additions & 0 deletions indexer/packages/notifications/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
SERVICE_NAME=notifications

FIREBASE_PROJECT_ID=projectID
FIREBASE_PRIVATE_KEY='-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----'
FIREBASE_CLIENT_EMAIL=[email protected]

DB_NAME=dydx_test
DB_USERNAME=dydx_test
DB_PASSWORD=dydxserver123
DB_PORT=5436
4 changes: 2 additions & 2 deletions indexer/packages/notifications/__tests__/localization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ describe('deriveLocalizedNotificationMessage', () => {
});

const expected = {
title: 'Your order for 1000 BTC/USD was filled at $45000',
body: 'Order Filled',
title: 'Order Filled',
body: 'Your order for 1000 BTC/USD was filled at $45000',
};

const result = deriveLocalizedNotificationMessage(notification);
Expand Down
73 changes: 73 additions & 0 deletions indexer/packages/notifications/__tests__/message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { logger } from '@dydxprotocol-indexer/base';
import { sendFirebaseMessage } from '../src/message';
import { sendMulticast } from '../src/lib/firebase';
import { createNotification, NotificationType } from '../src/types';
import { testMocks, dbHelpers } from '@dydxprotocol-indexer/postgres';
import { defaultToken } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants';

jest.mock('../src/lib/firebase', () => ({
sendMulticast: jest.fn(),
}));

describe('sendFirebaseMessage', () => {
let loggerInfoSpy: jest.SpyInstance;
let loggerWarnSpy: jest.SpyInstance;
let loggerErrorSpy: jest.SpyInstance;

beforeAll(() => {
loggerInfoSpy = jest.spyOn(logger, 'info').mockImplementation();
loggerWarnSpy = jest.spyOn(logger, 'warning').mockImplementation();
loggerErrorSpy = jest.spyOn(logger, 'error').mockImplementation();
});

afterAll(() => {
loggerInfoSpy.mockRestore();
loggerWarnSpy.mockRestore();
loggerErrorSpy.mockRestore();
});

beforeEach(async () => {
await testMocks.seedData();
});

afterEach(async () => {
await dbHelpers.clearData();
});

const mockNotification = createNotification(NotificationType.ORDER_FILLED, {
AMOUNT: '10',
MARKET: 'BTC-USD',
AVERAGE_PRICE: '100.50',
});

it('should send a Firebase message successfully', async () => {
await sendFirebaseMessage([defaultToken.token], mockNotification);

expect(sendMulticast).toHaveBeenCalled();
expect(logger.info).toHaveBeenCalledWith(expect.objectContaining({
message: 'Firebase message sent successfully',
notificationType: mockNotification.type,
}));
});

it('should log a warning if user has no registration tokens', async () => {
await sendFirebaseMessage([], mockNotification);

expect(logger.warning).toHaveBeenCalledWith(expect.objectContaining({
message: 'Attempted to send Firebase message to user with no registration tokens',
notificationType: mockNotification.type,
}));
});

it('should log an error if sending the message fails', async () => {
const mockedSendMulticast = sendMulticast as jest.MockedFunction<typeof sendMulticast>;
mockedSendMulticast.mockRejectedValueOnce(new Error('Send failed'));

await sendFirebaseMessage([defaultToken.token], mockNotification);

expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({
message: 'Failed to send Firebase message',
notificationType: mockNotification.type,
}));
});
});
4 changes: 2 additions & 2 deletions indexer/packages/notifications/src/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export function deriveLocalizedNotificationMessage(notification: Notification):
const tempLocalizationFields = {
[LocalizationKey.DEPOSIT_SUCCESS_TITLE]: 'Deposit Successful',
[LocalizationKey.DEPOSIT_SUCCESS_BODY]: 'You have successfully deposited {AMOUNT} {MARKET} to your dYdX account.',
[LocalizationKey.ORDER_FILLED_BODY]: 'Order Filled',
[LocalizationKey.ORDER_FILLED_TITLE]: 'Order Filled',
// eslint-disable-next-line no-template-curly-in-string
[LocalizationKey.ORDER_FILLED_TITLE]: 'Your order for {AMOUNT} {MARKET} was filled at ${AVERAGE_PRICE}',
[LocalizationKey.ORDER_FILLED_BODY]: 'Your order for {AMOUNT} {MARKET} was filled at ${AVERAGE_PRICE}',
// eslint-disable-next-line no-template-curly-in-string
[LocalizationKey.ORDER_TRIGGERED_BODY]: 'Your order for {AMOUNT} {MARKET} was triggered at ${PRICE}',
[LocalizationKey.ORDER_TRIGGERED_TITLE]: '{MARKET} Order Triggered',
Expand Down
33 changes: 14 additions & 19 deletions indexer/packages/notifications/src/message.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { logger } from '@dydxprotocol-indexer/base';
import { TokenTable } from '@dydxprotocol-indexer/postgres';

import {
MulticastMessage,
Expand All @@ -9,15 +8,20 @@ import { deriveLocalizedNotificationMessage } from './localization';
import { Notification } from './types';

export async function sendFirebaseMessage(
address: string,
tokens: string[],
notification: Notification,
): Promise<void> {
// Re-add once stats are implemented
// const start = Date.now();

const tokens = await getUserRegistrationTokens(address);
if (tokens.length === 0) {
throw new Error(`User has no registration tokens: ${address}`);
logger.warning({
at: 'notifications#firebase',
message: 'Attempted to send Firebase message to user with no registration tokens',
tokens,
notificationType: notification.type,
});
return;
}

const { title, body } = deriveLocalizedNotificationMessage(notification);
Expand Down Expand Up @@ -49,30 +53,21 @@ export async function sendFirebaseMessage(
try {
const result = await sendMulticast(message);
if (result?.failureCount && result?.failureCount > 0) {
logger.info({
at: 'notifications#firebase',
message: `Failed to send Firebase message: ${JSON.stringify(message)}`,
result,
address,
notificationType: notification.type,
});
throw new Error('Failed to send Firebase message');
}
} catch (error) {
logger.error({
at: 'notifications#firebase',
message: `Failed to send Firebase message: ${JSON.stringify(message)}`,
message: 'Failed to send Firebase message',
error: error as Error,
address,
notificationType: notification.type,
});
throw new Error('Failed to send Firebase message');
} finally {
// stats.timing(`${config.SERVICE_NAME}.send_firebase_message.timing`, Date.now() - start);
logger.info({
at: 'notifications#firebase',
message: 'Firebase message sent successfully',
notificationType: notification.type,
});
}
}

async function getUserRegistrationTokens(address: string): Promise<string[]> {
const token = await TokenTable.findAll({ address, limit: 10 }, []);
return token.map((t) => t.token);
}
3 changes: 1 addition & 2 deletions indexer/pnpm-lock.yaml

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

Original file line number Diff line number Diff line change
Expand Up @@ -388,18 +388,23 @@ class AddressesController extends Controller {
public async testNotification(
@Path() address: string,
): Promise<void> {
const wallet = await WalletTable.findById(address);
if (!wallet) {
throw new NotFoundError(`No wallet found for address: ${address}`);
}

try {
const wallet = await WalletTable.findById(address);
if (!wallet) {
throw new NotFoundError(`No wallet found for address: ${address}`);
}
const allTokens = await TokenTable.findAll({ address: wallet.address }, [])
.then((tokens) => tokens.map((token) => token.token));
if (allTokens.length === 0) {
throw new NotFoundError(`No tokens found for address: ${address}`);
}

const notification = createNotification(NotificationType.ORDER_FILLED, {
[NotificationDynamicFieldKey.MARKET]: 'BTC/USD',
[NotificationDynamicFieldKey.AMOUNT]: '100',
[NotificationDynamicFieldKey.AVERAGE_PRICE]: '1000',
});
await sendFirebaseMessage(wallet.address, notification);
await sendFirebaseMessage(allTokens, notification);
} catch (error) {
throw new Error('Failed to send test notification');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from '@dydxprotocol-indexer/base';
import {
createNotification,
NotificationDynamicFieldKey,
Expand All @@ -16,42 +17,62 @@ export async function sendOrderFilledNotification(
order: OrderFromDatabase,
market: PerpetualMarketFromDatabase,
) {
const subaccount = await SubaccountTable.findById(order.subaccountId);
if (!subaccount) {
throw new Error(`Subaccount not found for id ${order.subaccountId}`);
}
try {
const subaccount = await SubaccountTable.findById(order.subaccountId);
if (!subaccount) {
throw new Error(`Subaccount not found for id ${order.subaccountId}`);
}

const tokens = (await TokenTable.findAll({ address: subaccount.address }, []))
.map((token) => token.token);
if (tokens.length === 0) {
throw new Error(`No token found for address ${subaccount.address}`);
}

const notification = createNotification(
NotificationType.ORDER_FILLED,
{
[NotificationDynamicFieldKey.AMOUNT]: order.size.toString(),
[NotificationDynamicFieldKey.MARKET]: market.ticker,
[NotificationDynamicFieldKey.AVERAGE_PRICE]: order.price,
},
);

const token = await TokenTable.findAll({ address: subaccount.address, limit: 1 }, []);
if (token.length === 0) {
return;
await sendFirebaseMessage(tokens, notification);
} catch (error) {
logger.error({
at: 'ender#notification-functions',
message: 'Error sending order filled notification',
error,
});
}
const notification = createNotification(
NotificationType.ORDER_FILLED,
{
[NotificationDynamicFieldKey.AMOUNT]: order.size.toString(),
[NotificationDynamicFieldKey.MARKET]: market.ticker,
[NotificationDynamicFieldKey.AVERAGE_PRICE]: order.price,
},
);
await sendFirebaseMessage(subaccount.address, notification);
}

export async function sendOrderTriggeredNotification(
order: OrderFromDatabase,
market: PerpetualMarketFromDatabase,
subaccount: SubaccountFromDatabase,
) {
const token = await TokenTable.findAll({ address: subaccount.address, limit: 1 }, []);
if (token.length === 0) {
return;
try {
const tokens = (await TokenTable.findAll({ address: subaccount.address }, []))
.map((token) => token.token);
if (tokens.length === 0) {
throw new Error(`No tokens found for address ${subaccount.address}`);
}
const notification = createNotification(
NotificationType.ORDER_TRIGGERED,
{
[NotificationDynamicFieldKey.MARKET]: market.ticker,
[NotificationDynamicFieldKey.PRICE]: order.price,
[NotificationDynamicFieldKey.AMOUNT]: order.size.toString(),
},
);
await sendFirebaseMessage(tokens, notification);
} catch (error) {
logger.error({
at: 'ender#notification-functions',
message: 'Error sending order triggered notification',
error,
});
}
const notification = createNotification(
NotificationType.ORDER_TRIGGERED,
{
[NotificationDynamicFieldKey.MARKET]: market.ticker,
[NotificationDynamicFieldKey.PRICE]: order.price,
[NotificationDynamicFieldKey.AMOUNT]: order.size.toString(),
},
);
await sendFirebaseMessage(subaccount.address, notification);
}

0 comments on commit a11b478

Please sign in to comment.