diff --git a/.env.dist b/.env.dist index d46d9b9..2b50f2b 100644 --- a/.env.dist +++ b/.env.dist @@ -7,3 +7,6 @@ ALICE_BOB_PUBLIC_KEY= ALICE_BOB_PRIVATE_KEY= THREE_ROUTE_API_URL= THREE_ROUTE_API_AUTH_TOKEN= +REDIS_URL= +ADD_NOTIFICATION_USERNAME= +ADD_NOTIFICATION_PASSWORD= diff --git a/src/notifications/notifications-list.data.ts b/migrations/notifications/data/notifications.data.ts similarity index 83% rename from src/notifications/notifications-list.data.ts rename to migrations/notifications/data/notifications.data.ts index 5838269..b845f7c 100644 --- a/src/notifications/notifications-list.data.ts +++ b/migrations/notifications/data/notifications.data.ts @@ -1,23 +1,7 @@ -import { Notification, NotificationType, PlatformType } from './notification.interface'; +import { DEFAULT_IMAGE_URLS } from '../../../src/notifications/default-image-fallbacks'; +import { Notification, NotificationType, PlatformType } from '../../../src/notifications/notification.interface'; -const BANNERS_BUCKET_URL = 'https://generic-objects.fra1.digitaloceanspaces.com/notification-icons'; - -const DEFAULT_BANNER_URLS = { - extension: { - news: `${BANNERS_BUCKET_URL}/extension/news.svg`, - platformUpdate: `${BANNERS_BUCKET_URL}/extension/platform-update.svg`, - securityNote: `${BANNERS_BUCKET_URL}/extension/security-note.svg`, - winNft: `${BANNERS_BUCKET_URL}/extension/extension-win-nft.svg` - }, - mobile: { - news: `${BANNERS_BUCKET_URL}/mobile/news.svg`, - platformUpdate: `${BANNERS_BUCKET_URL}/mobile/platform-update.svg`, - securityNote: `${BANNERS_BUCKET_URL}/mobile/security-note.svg`, - winNft: `${BANNERS_BUCKET_URL}/mobile/mobile-win-nft.svg` - } -}; - -export const NOTIFICATIONS_LIST: Notification[] = [ +export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ { id: 4, createdAt: '2022-12-29T16:00:00.000Z', @@ -35,8 +19,8 @@ export const NOTIFICATIONS_LIST: Notification[] = [ '\n', 'Check whether your mobile app has the latest version.' ], - extensionImageUrl: DEFAULT_BANNER_URLS.extension.news, - mobileImageUrl: DEFAULT_BANNER_URLS.mobile.news + extensionImageUrl: DEFAULT_IMAGE_URLS.extension.news, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news }, { id: 3, @@ -62,8 +46,8 @@ export const NOTIFICATIONS_LIST: Notification[] = [ url: 'https://story.madfish.solutions/discover-the-tezos-nft-world-and-stand-a-chance-to-win-an-nft-artwork-by-the-famous-artist-mario-klingemann/' } ], - extensionImageUrl: DEFAULT_BANNER_URLS.extension.winNft, - mobileImageUrl: DEFAULT_BANNER_URLS.mobile.winNft + extensionImageUrl: DEFAULT_IMAGE_URLS.extension.winNft, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.winNft } ]; @@ -99,8 +83,9 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { text: 'here', url: 'https://madfish.crunch.help/temple-wallet/a-note-on-security' }, '.' ], - extensionImageUrl: DEFAULT_BANNER_URLS.extension.securityNote, - mobileImageUrl: DEFAULT_BANNER_URLS.mobile.securityNote + extensionImageUrl: DEFAULT_IMAGE_URLS.extension.securityNote, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.securityNote, + isMandatory: true }, { id: 1, @@ -132,8 +117,9 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { text: 'here', url: 'https://madfish.crunch.help/temple-wallet/a-note-on-security' }, '.' ], - extensionImageUrl: DEFAULT_BANNER_URLS.extension.securityNote, - mobileImageUrl: DEFAULT_BANNER_URLS.mobile.securityNote + extensionImageUrl: DEFAULT_IMAGE_URLS.extension.securityNote, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.securityNote, + isMandatory: true }, { id: 0, @@ -175,7 +161,8 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { text: 'Discord', url: 'https://discord.com/invite/qFRZ8kVzkv' }, '. We’re happy to have you!\n' ], - extensionImageUrl: DEFAULT_BANNER_URLS.extension.news, - mobileImageUrl: DEFAULT_BANNER_URLS.mobile.news + extensionImageUrl: DEFAULT_IMAGE_URLS.extension.news, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news, + isMandatory: true } ]; diff --git a/migrations/notifications/index.ts b/migrations/notifications/index.ts new file mode 100644 index 0000000..ec50573 --- /dev/null +++ b/migrations/notifications/index.ts @@ -0,0 +1,16 @@ +import '../../src/configure'; + +import { Redis } from 'ioredis'; +import { EnvVars } from '../../src/config'; +import { addExistingNotificationsToDb } from './utils/add-existing-notifications-to-db'; +import logger from '../../src/utils/logger'; + +const redisClient = new Redis(EnvVars.REDIS_URL); + +redisClient.on('error', err => logger.error(err)); + +(async () => { + await addExistingNotificationsToDb(redisClient); + logger.info('Notifications successfully added to Redis database.'); + process.exit(0); +})(); diff --git a/migrations/notifications/utils/add-existing-notifications-to-db.ts b/migrations/notifications/utils/add-existing-notifications-to-db.ts new file mode 100644 index 0000000..d011751 --- /dev/null +++ b/migrations/notifications/utils/add-existing-notifications-to-db.ts @@ -0,0 +1,14 @@ +import { Redis } from 'ioredis'; + +import { DEFAULT_NOTIFICATIONS_LIST, MANDATORY_NOTIFICATIONS_LIST } from '../data/notifications.data'; + +export const addExistingNotificationsToDb = async (client: Redis) => { + const data = await client.lrange('notifications', 0, -1); + const existingNotifications = [...DEFAULT_NOTIFICATIONS_LIST, ...MANDATORY_NOTIFICATIONS_LIST]; + + if (data.length === 0) { + for (let i = 0; i < existingNotifications.length; i++) { + await client.rpush('notifications', JSON.stringify(existingNotifications[i])); + } + } +}; diff --git a/package.json b/package.json index 64eaf9f..8a8f9fc 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,13 @@ "@taquito/utils": "14.0.0", "axios": "^0.27.2", "bignumber.js": "^9.1.0", + "body-parser": "^1.20.2", "cors": "^2.8.5", "cross-fetch": "^3.1.5", "dotenv": "^9.0.2", "express": "^4.18.2", "firebase-admin": "^10.0.2", + "ioredis": "^5.3.2", "memoizee": "^0.4.15", "pino": "^6.11.2", "pino-http": "^5.5.0", @@ -32,9 +34,11 @@ "ts": "tsc --pretty", "lint": "eslint ./src --ext .js,.ts", "lint:fix": "eslint ./src --ext .js,.ts --fix", - "clean": "rimraf dist/" + "clean": "rimraf dist/", + "db-migration": "cd migrations/notifications && npx ts-node index.ts" }, "devDependencies": { + "@types/body-parser": "^1.19.2", "@types/express": "^4.17.17", "@types/express-jwt": "^7.4.2", "@types/express-unless": "^2.0.1", diff --git a/src/config.ts b/src/config.ts index 85f6c36..4cfd913 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,21 +4,17 @@ import { isDefined } from './utils/helpers'; export const MIN_IOS_APP_VERSION = '1.10.445'; export const MIN_ANDROID_APP_VERSION = '1.10.445'; -export const MOONPAY_SECRET_KEY = getEnv('MOONPAY_SECRET_KEY'); -export const ALICE_BOB_PRIVATE_KEY = getEnv('ALICE_BOB_PRIVATE_KEY'); -export const ALICE_BOB_PUBLIC_KEY = getEnv('ALICE_BOB_PUBLIC_KEY'); -export const THREE_ROUTE_API_URL = getEnv('THREE_ROUTE_API_URL'); -export const THREE_ROUTE_API_AUTH_TOKEN = getEnv('THREE_ROUTE_API_AUTH_TOKEN'); +export const EnvVars = { + MOONPAY_SECRET_KEY: getEnv('MOONPAY_SECRET_KEY'), + ALICE_BOB_PRIVATE_KEY: getEnv('ALICE_BOB_PRIVATE_KEY'), + ALICE_BOB_PUBLIC_KEY: getEnv('ALICE_BOB_PUBLIC_KEY'), + THREE_ROUTE_API_URL: getEnv('THREE_ROUTE_API_URL'), + THREE_ROUTE_API_AUTH_TOKEN: getEnv('THREE_ROUTE_API_AUTH_TOKEN'), + REDIS_URL: getEnv('REDIS_URL'), + ADD_NOTIFICATION_USERNAME: getEnv('ADD_NOTIFICATION_USERNAME'), + ADD_NOTIFICATION_PASSWORD: getEnv('ADD_NOTIFICATION_PASSWORD') +}; -const variablesToAssert = [ - { name: 'MOONPAY_SECRET_KEY', value: MOONPAY_SECRET_KEY }, - { name: 'ALICE_BOB_PRIVATE_KEY', value: ALICE_BOB_PRIVATE_KEY }, - { name: 'ALICE_BOB_PUBLIC_KEY', value: ALICE_BOB_PUBLIC_KEY }, - { name: 'THREE_ROUTE_API_URL', value: THREE_ROUTE_API_URL }, - { name: 'THREE_ROUTE_API_AUTH_TOKEN', value: THREE_ROUTE_API_AUTH_TOKEN } -]; -variablesToAssert.forEach(({ name, value }) => { - if (!isDefined(value)) { - throw new Error(`process.env.${name} not found.`); - } -}); +for (const name in EnvVars) { + if (!isDefined(EnvVars[name])) throw new Error(`process.env.${name} is not set.`); +} diff --git a/src/index.ts b/src/index.ts index 9e2eefe..ac0c5cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ require('./configure'); +import bodyParser from 'body-parser'; import cors from 'cors'; import express, { Request, Response } from 'express'; import firebaseAdmin from 'firebase-admin'; @@ -9,8 +10,13 @@ import pinoHttp from 'pino-http'; import { getAdvertisingInfo } from './advertising/advertising'; import { MIN_ANDROID_APP_VERSION, MIN_IOS_APP_VERSION } from './config'; import getDAppsStats from './getDAppsStats'; -import { PlatformType } from './notifications/notification.interface'; -import { getNotifications } from './notifications/notifications.utils'; +import { basicAuth } from './middlewares/basic-auth.middleware'; +import { Notification, PlatformType } from './notifications/notification.interface'; +import { getImageFallback } from './notifications/utils/get-image-fallback.util'; +import { getNotifications } from './notifications/utils/get-notifications.util'; +import { getParsedContent } from './notifications/utils/get-parsed-content.util'; +import { getPlatforms } from './notifications/utils/get-platforms.util'; +import { redisClient } from './redis'; import { getABData } from './utils/ab-test'; import { cancelAliceBobOrder } from './utils/alice-bob/cancel-alice-bob-order'; import { createAliceBobOrder } from './utils/alice-bob/create-alice-bob-order'; @@ -18,7 +24,7 @@ import { estimateAliceBobOutput } from './utils/alice-bob/estimate-alice-bob-out import { getAliceBobOrderInfo } from './utils/alice-bob/get-alice-bob-order-info'; import { getAliceBobPairInfo } from './utils/alice-bob/get-alice-bob-pair-info'; import { coinGeckoTokens } from './utils/gecko-tokens'; -import { getExternalApiErrorPayload } from './utils/helpers'; +import { getExternalApiErrorPayload, isDefined, isNonEmptyString } from './utils/helpers'; import logger from './utils/logger'; import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url'; import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; @@ -50,6 +56,7 @@ const PINO_LOGGER = { const app = express(); app.use(pinoHttp(PINO_LOGGER)); app.use(cors()); +app.use(bodyParser.json()); const dAppsProvider = new SingleQueryDataProvider(15 * 60 * 1000, getDAppsStats); @@ -91,10 +98,11 @@ app.get('/api/top-coins', (_req, res) => { res.status(200).send(coinGeckoTokens); }); -app.get('/api/notifications', (_req, res) => { +app.get('/api/notifications', async (_req, res) => { try { const { platform, startFromTime } = _req.query; - const data = getNotifications( + const data = await getNotifications( + redisClient, platform === PlatformType.Mobile ? PlatformType.Mobile : PlatformType.Extension, Number(startFromTime) ?? 0 ); @@ -105,6 +113,47 @@ app.get('/api/notifications', (_req, res) => { } }); +app.post('/api/notifications', basicAuth, async (req, res) => { + try { + const { + mobile, + extension, + type, + title, + description, + extensionImageUrl, + mobileImageUrl, + content, + date, + expirationDate, + isMandatory + } = req.body; + + const newNotification: Notification = { + id: Date.now(), + createdAt: date, + type, + platforms: getPlatforms(mobile, extension), + language: 'en-US', + title, + description, + content: getParsedContent(content), + extensionImageUrl: isNonEmptyString(extensionImageUrl) + ? extensionImageUrl + : getImageFallback(PlatformType.Extension, type), + mobileImageUrl: isNonEmptyString(mobileImageUrl) ? mobileImageUrl : getImageFallback(PlatformType.Mobile, type), + expirationDate, + isMandatory: isDefined(isMandatory) + }; + + await redisClient.lpush('notifications', JSON.stringify(newNotification)); + + res.status(200).send({ message: 'Notification added successfully' }); + } catch (error: any) { + res.status(500).send({ error: error.message }); + } +}); + app.get('/api/dapps', makeProviderDataRequestHandler(dAppsProvider)); app.get('/api/abtest', (_, res) => { diff --git a/src/interfaces/alice-bob/alice-bob.interfaces.ts b/src/interfaces/alice-bob.interfaces.ts similarity index 100% rename from src/interfaces/alice-bob/alice-bob.interfaces.ts rename to src/interfaces/alice-bob.interfaces.ts diff --git a/src/interfaces/contract-factoctories.ts b/src/interfaces/contract-factoctories.interfaces.ts similarity index 100% rename from src/interfaces/contract-factoctories.ts rename to src/interfaces/contract-factoctories.interfaces.ts diff --git a/src/interfaces/price-history.ts b/src/interfaces/price-history.interfaces.ts similarity index 100% rename from src/interfaces/price-history.ts rename to src/interfaces/price-history.interfaces.ts diff --git a/src/interfaces/tickers.ts b/src/interfaces/ticker.interface.ts similarity index 100% rename from src/interfaces/tickers.ts rename to src/interfaces/ticker.interface.ts diff --git a/src/middlewares/basic-auth.middleware.ts b/src/middlewares/basic-auth.middleware.ts new file mode 100644 index 0000000..a9d7ad5 --- /dev/null +++ b/src/middlewares/basic-auth.middleware.ts @@ -0,0 +1,25 @@ +import { Request, Response, NextFunction } from 'express'; + +import { EnvVars } from '../config'; +import { isDefined } from '../utils/helpers'; + +export const basicAuth = (req: Request, res: Response, next: NextFunction) => { + const base64EncodedCredentials = req.get('Authorization'); + + if (isDefined(base64EncodedCredentials)) { + const [username, password] = Buffer.from(base64EncodedCredentials.split(' ')[1], 'base64').toString().split(':'); + + if (!(username === EnvVars.ADD_NOTIFICATION_USERNAME && password === EnvVars.ADD_NOTIFICATION_PASSWORD)) { + handleNotAuthenticated(res, next); + } + next(); + } else { + handleNotAuthenticated(res, next); + } +}; + +const handleNotAuthenticated = (res: Response, next: NextFunction) => { + const err = new Error('Not Authenticated!'); + res.status(401).set('WWW-Authenticate', 'Basic'); + next(err); +}; diff --git a/src/notifications/default-image-fallbacks.ts b/src/notifications/default-image-fallbacks.ts new file mode 100644 index 0000000..a725041 --- /dev/null +++ b/src/notifications/default-image-fallbacks.ts @@ -0,0 +1,16 @@ +const IMAGES_BUCKET_URL = 'https://generic-objects.fra1.digitaloceanspaces.com/notification-icons'; + +export const DEFAULT_IMAGE_URLS = { + extension: { + news: `${IMAGES_BUCKET_URL}/extension/news.svg`, + platformUpdate: `${IMAGES_BUCKET_URL}/extension/platform-update.svg`, + securityNote: `${IMAGES_BUCKET_URL}/extension/security-note.svg`, + winNft: `${IMAGES_BUCKET_URL}/extension/extension-win-nft.svg` + }, + mobile: { + news: `${IMAGES_BUCKET_URL}/mobile/news.svg`, + platformUpdate: `${IMAGES_BUCKET_URL}/mobile/platform-update.svg`, + securityNote: `${IMAGES_BUCKET_URL}/mobile/security-note.svg`, + winNft: `${IMAGES_BUCKET_URL}/mobile/mobile-win-nft.svg` + } +}; diff --git a/src/notifications/notification.interface.ts b/src/notifications/notification.interface.ts index cdbc9b3..d4968aa 100644 --- a/src/notifications/notification.interface.ts +++ b/src/notifications/notification.interface.ts @@ -9,7 +9,7 @@ export enum PlatformType { Extension = 'Extension' } -interface NotificationLink { +export interface NotificationLink { text: string; url: string; } @@ -26,4 +26,6 @@ export interface Notification { extensionImageUrl: string; mobileImageUrl: string; sourceUrl?: string; + expirationDate?: string; + isMandatory?: boolean; } diff --git a/src/notifications/notifications.utils.ts b/src/notifications/notifications.utils.ts deleted file mode 100644 index b44bc30..0000000 --- a/src/notifications/notifications.utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PlatformType } from './notification.interface'; -import { MANDATORY_NOTIFICATIONS_LIST, NOTIFICATIONS_LIST } from './notifications-list.data'; - -export const getNotifications = (platform: PlatformType, startFromTime: number) => - [ - ...NOTIFICATIONS_LIST.filter(notification => new Date(notification.createdAt).getTime() > startFromTime), - ...MANDATORY_NOTIFICATIONS_LIST.map(notification => ({ - ...notification, - createdAt: new Date(startFromTime).toString() - })) - ].filter(notification => notification.platforms.includes(platform)); diff --git a/src/notifications/utils/get-image-fallback.util.ts b/src/notifications/utils/get-image-fallback.util.ts new file mode 100644 index 0000000..2957100 --- /dev/null +++ b/src/notifications/utils/get-image-fallback.util.ts @@ -0,0 +1,24 @@ +import { DEFAULT_IMAGE_URLS } from '../default-image-fallbacks'; +import { NotificationType, PlatformType } from '../notification.interface'; + +export const getImageFallback = (platform: PlatformType, notificationType: NotificationType) => { + if (platform === PlatformType.Mobile) { + switch (notificationType) { + case NotificationType.PlatformUpdate: + return DEFAULT_IMAGE_URLS.mobile.platformUpdate; + case NotificationType.SecurityNote: + return DEFAULT_IMAGE_URLS.mobile.securityNote; + default: + return DEFAULT_IMAGE_URLS.mobile.news; + } + } else { + switch (notificationType) { + case NotificationType.PlatformUpdate: + return DEFAULT_IMAGE_URLS.extension.platformUpdate; + case NotificationType.SecurityNote: + return DEFAULT_IMAGE_URLS.extension.securityNote; + default: + return DEFAULT_IMAGE_URLS.extension.news; + } + } +}; diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts new file mode 100644 index 0000000..d6ae3a3 --- /dev/null +++ b/src/notifications/utils/get-notifications.util.ts @@ -0,0 +1,33 @@ +import { Redis } from 'ioredis'; + +import { isNonEmptyString } from '../../utils/helpers'; +import { Notification, PlatformType } from '../notification.interface'; + +export const getNotifications = async (client: Redis, platform: PlatformType, startFromTime: number) => { + const data = await client.lrange('notifications', 0, -1); + const notifications: Notification[] = data + .map(item => JSON.parse(item)) + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + + const now = Date.now(); + const result: Notification[] = []; + + for (let i = 0; i < notifications.length; i++) { + const { isMandatory, createdAt, platforms, expirationDate } = notifications[i]; + const createdAtTimestamp = new Date(createdAt).getTime(); + + if (isNonEmptyString(expirationDate) && new Date(expirationDate).getTime() < now) { + await client.lrem('notifications', 1, JSON.stringify(notifications[i])); + continue; + } + + if ( + platforms.includes(platform) && + (isMandatory === true || (createdAtTimestamp > startFromTime && createdAtTimestamp < now)) + ) { + result.push(notifications[i]); + } + } + + return result; +}; diff --git a/src/notifications/utils/get-parsed-content.util.ts b/src/notifications/utils/get-parsed-content.util.ts new file mode 100644 index 0000000..5bb2f45 --- /dev/null +++ b/src/notifications/utils/get-parsed-content.util.ts @@ -0,0 +1,66 @@ +import { NotificationLink } from '../notification.interface'; + +// Parses text input string (that could contain links, list and indents) to a proper data structure +export const getParsedContent = (content: string): Array => + content + .split('\r\n') + .map((item: string, index: number, array: string[]) => { + if (item.length === 0 && array[index - 1].startsWith(' • ')) { + item += '\n'; + } else if (item.length === 0) { + item += '\n\n'; + } + + if (item.endsWith(':') || item.startsWith(' • ')) { + item += '\n'; + } + + if (item.includes('{')) { + const regex = /{[^}]+}/g; + const objects = item.match(regex) || []; + + const result: (string | NotificationLink)[] = []; + + let lastIndex = 0; + for (const obj of objects) { + const startIndex = item.indexOf(obj, lastIndex); + if (startIndex > lastIndex) { + result.push(item.substring(lastIndex, startIndex)); + } + result.push(JSON.parse(obj)); + lastIndex = startIndex + obj.length; + } + + if (lastIndex < item.length) { + result.push(item.substring(lastIndex)); + } + + return result; + } + + return item; + }) + .flat(); + +// Example: + +// in: +// "It boasts all the features you would expect from a modern crypto wallet: +// • Top up balance with crypto or credit card. +// • Sync your wallet between mobile and desktop devices. +// +// To quickly learn the ropes, check our { text: 'knowledge base', url: 'https://madfish.crunch.help' } and { text: 'YouTube video tutorials', url: 'https://www.youtube.com' } out." + +// out: +// [ 'It boasts all the features you would expect from a modern crypto wallet:\n', +// ' • Top up balance with crypto or credit card.\n', +// ' • Sync your wallet between mobile and desktop devices.\n', +// '\n', +// 'To quickly learn the ropes, check our ', +// { text: 'knowledge base', url: 'https://madfish.crunch.help' }, +// ' and ', +// { +// text: 'YouTube video tutorials', +// url: 'https://www.youtube.com' +// }, +// ' out.\n' ] diff --git a/src/notifications/utils/get-platforms.util.ts b/src/notifications/utils/get-platforms.util.ts new file mode 100644 index 0000000..4bcec2c --- /dev/null +++ b/src/notifications/utils/get-platforms.util.ts @@ -0,0 +1,16 @@ +import { isDefined } from '../../utils/helpers'; +import { PlatformType } from '../notification.interface'; + +export const getPlatforms = (mobile?: string, extension?: string) => { + let platforms: PlatformType[]; + + if (isDefined(mobile) && isDefined(extension)) { + platforms = [PlatformType.Mobile, PlatformType.Extension]; + } else if (isDefined(mobile)) { + platforms = [PlatformType.Mobile]; + } else { + platforms = [PlatformType.Extension]; + } + + return platforms; +}; diff --git a/src/redis.ts b/src/redis.ts new file mode 100644 index 0000000..57a72d7 --- /dev/null +++ b/src/redis.ts @@ -0,0 +1,7 @@ +import { Redis } from 'ioredis'; + +import { EnvVars } from './config'; +import logger from './utils/logger'; + +export const redisClient = new Redis(EnvVars.REDIS_URL); +redisClient.on('error', err => logger.error(err)); diff --git a/src/utils/alice-bob/cancel-alice-bob-order.ts b/src/utils/alice-bob/cancel-alice-bob-order.ts index 4e86630..9ba1079 100644 --- a/src/utils/alice-bob/cancel-alice-bob-order.ts +++ b/src/utils/alice-bob/cancel-alice-bob-order.ts @@ -1,4 +1,4 @@ -import { AliceBobCancelOrderPayload } from '../../interfaces/alice-bob/alice-bob.interfaces'; +import { AliceBobCancelOrderPayload } from '../../interfaces/alice-bob.interfaces'; import { aliceBobApi } from '../api.sevice'; import { getAliceBobRequestHeaders } from './get-alice-bob-request-headers'; import { getAliceBobSignature } from './get-alice-bob-signature'; diff --git a/src/utils/alice-bob/create-alice-bob-order.ts b/src/utils/alice-bob/create-alice-bob-order.ts index d36e6ea..d97999c 100644 --- a/src/utils/alice-bob/create-alice-bob-order.ts +++ b/src/utils/alice-bob/create-alice-bob-order.ts @@ -1,4 +1,4 @@ -import { AliceBobCreateOrderPayload, aliceBobOrder } from '../../interfaces/alice-bob/alice-bob.interfaces'; +import { AliceBobCreateOrderPayload, aliceBobOrder } from '../../interfaces/alice-bob.interfaces'; import { aliceBobApi } from '../api.sevice'; import { getAliceBobRequestHeaders } from './get-alice-bob-request-headers'; import { getAliceBobSignature } from './get-alice-bob-signature'; diff --git a/src/utils/alice-bob/estimate-alice-bob-output.ts b/src/utils/alice-bob/estimate-alice-bob-output.ts index 4272a1c..5d9a7c3 100644 --- a/src/utils/alice-bob/estimate-alice-bob-output.ts +++ b/src/utils/alice-bob/estimate-alice-bob-output.ts @@ -1,4 +1,4 @@ -import { AliceBobEstimateAmountPayload } from '../../interfaces/alice-bob/alice-bob.interfaces'; +import { AliceBobEstimateAmountPayload } from '../../interfaces/alice-bob.interfaces'; import { aliceBobApi } from '../api.sevice'; import { getAliceBobRequestHeaders } from './get-alice-bob-request-headers'; import { getAliceBobSignature } from './get-alice-bob-signature'; diff --git a/src/utils/alice-bob/get-alice-bob-order-info.ts b/src/utils/alice-bob/get-alice-bob-order-info.ts index 5de845b..2abe1b7 100644 --- a/src/utils/alice-bob/get-alice-bob-order-info.ts +++ b/src/utils/alice-bob/get-alice-bob-order-info.ts @@ -1,4 +1,4 @@ -import { aliceBobOrder } from '../../interfaces/alice-bob/alice-bob.interfaces'; +import { aliceBobOrder } from '../../interfaces/alice-bob.interfaces'; import { aliceBobApi } from '../api.sevice'; import { getAliceBobRequestHeaders } from './get-alice-bob-request-headers'; import { getAliceBobSignature } from './get-alice-bob-signature'; diff --git a/src/utils/alice-bob/get-alice-bob-pair-info.ts b/src/utils/alice-bob/get-alice-bob-pair-info.ts index e5c68f1..35fd0bf 100644 --- a/src/utils/alice-bob/get-alice-bob-pair-info.ts +++ b/src/utils/alice-bob/get-alice-bob-pair-info.ts @@ -1,6 +1,6 @@ import { AxiosError } from 'axios'; -import { AliceBobPairInfo } from '../../interfaces/alice-bob/alice-bob.interfaces'; +import { AliceBobPairInfo } from '../../interfaces/alice-bob.interfaces'; import { aliceBobApi } from '../api.sevice'; import { estimateAliceBobOutput } from './estimate-alice-bob-output'; import { getAliceBobRequestHeaders } from './get-alice-bob-request-headers'; diff --git a/src/utils/alice-bob/get-alice-bob-request-headers.ts b/src/utils/alice-bob/get-alice-bob-request-headers.ts index 6d68647..70fc927 100644 --- a/src/utils/alice-bob/get-alice-bob-request-headers.ts +++ b/src/utils/alice-bob/get-alice-bob-request-headers.ts @@ -1,7 +1,7 @@ -import { ALICE_BOB_PUBLIC_KEY } from '../../config'; +import { EnvVars } from '../../config'; export const getAliceBobRequestHeaders = (signature: string, now: number) => ({ - 'public-key': ALICE_BOB_PUBLIC_KEY, + 'public-key': EnvVars.ALICE_BOB_PUBLIC_KEY, timestamp: now, signature }); diff --git a/src/utils/alice-bob/get-alice-bob-signature.ts b/src/utils/alice-bob/get-alice-bob-signature.ts index 9ad6ba4..8dc396b 100644 --- a/src/utils/alice-bob/get-alice-bob-signature.ts +++ b/src/utils/alice-bob/get-alice-bob-signature.ts @@ -1,7 +1,7 @@ import crypto from 'crypto'; -import { ALICE_BOB_PRIVATE_KEY } from '../../config'; -import { AliceBobPayload } from '../../interfaces/alice-bob/alice-bob.interfaces'; +import { EnvVars } from '../../config'; +import { AliceBobPayload } from '../../interfaces/alice-bob.interfaces'; export const getAliceBobSignature = (payload?: AliceBobPayload) => { const now = Date.now(); @@ -24,7 +24,7 @@ export const getAliceBobSignature = (payload?: AliceBobPayload) => { initString += 'timestamp' + now; return { - signature: crypto.createHmac('SHA512', ALICE_BOB_PRIVATE_KEY).update(initString).digest('hex'), + signature: crypto.createHmac('SHA512', EnvVars.ALICE_BOB_PRIVATE_KEY).update(initString).digest('hex'), now }; }; diff --git a/src/utils/dapp-list-constants.ts b/src/utils/dapp-list-constants.ts index df39148..38d7375 100644 --- a/src/utils/dapp-list-constants.ts +++ b/src/utils/dapp-list-constants.ts @@ -144,13 +144,5 @@ export const dappList: DappList[] = [ logo: 'https://pbs.twimg.com/profile_images/1538616711536156672/eRwz1uNE_400x400.jpg', slug: 'kordfi', categories: [DappType.DeFi] - }, - { - name: 'picky.fi', - dappUrl: 'https://www.picky.fi/', - type: DappType.DeFi, - logo: 'https://pbs.twimg.com/profile_images/1560967275515187203/50uGAS2F_400x400.png', - slug: 'pickyfi', - categories: [DappType.DeFi] } ]; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index c7f612a..2335fba 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -34,6 +34,8 @@ export const emptyFn = () => {}; export const isDefined = (value: T | undefined | null): value is T => value !== undefined && value !== null; +export const isNonEmptyString = (str: unknown): str is string => typeof str === 'string' && str.length !== 0; + export const sleep = (ms: number) => new Promise(resolve => setTimeout(() => resolve('wake'), ms)); export const getExternalApiErrorPayload = (error: unknown) => { diff --git a/src/utils/moonpay/get-signed-moonpay-url.ts b/src/utils/moonpay/get-signed-moonpay-url.ts index d9e0ddc..1ccd0bd 100644 --- a/src/utils/moonpay/get-signed-moonpay-url.ts +++ b/src/utils/moonpay/get-signed-moonpay-url.ts @@ -1,10 +1,10 @@ import crypto from 'crypto'; -import { MOONPAY_SECRET_KEY } from '../../config'; +import { EnvVars } from '../../config'; export const getSignedMoonPayUrl = (originalUrl: string) => { const signature = crypto - .createHmac('sha256', MOONPAY_SECRET_KEY) + .createHmac('sha256', EnvVars.MOONPAY_SECRET_KEY) .update(new URL(originalUrl).search) .digest('base64'); diff --git a/src/utils/tezos.ts b/src/utils/tezos.ts index b5945f8..e279b26 100644 --- a/src/utils/tezos.ts +++ b/src/utils/tezos.ts @@ -3,7 +3,7 @@ import { tzip12 } from '@taquito/tzip12'; import { tzip16 } from '@taquito/tzip16'; import memoizee from 'memoizee'; -import { ITicker } from '../interfaces/tickers'; +import { ITicker } from '../interfaces/ticker.interface'; import fetch from './fetch'; import SingleQueryDataProvider from './SingleQueryDataProvider'; import { BcdTokenData } from './tzkt'; diff --git a/src/utils/three-route.ts b/src/utils/three-route.ts index e89fcc7..4782feb 100644 --- a/src/utils/three-route.ts +++ b/src/utils/three-route.ts @@ -1,4 +1,4 @@ -import { THREE_ROUTE_API_AUTH_TOKEN, THREE_ROUTE_API_URL } from '../config'; +import { EnvVars } from '../config'; import { makeBuildQueryFn } from './makeBuildQueryFn'; interface SwapQueryParams { @@ -106,9 +106,9 @@ type ThreeRouteQueryResponse = export const THREE_ROUTE_SIRS_SYMBOL = 'SIRS'; const threeRouteBuildQueryFn = makeBuildQueryFn( - THREE_ROUTE_API_URL, + EnvVars.THREE_ROUTE_API_URL, 5, - { headers: { Authorization: `Basic ${THREE_ROUTE_API_AUTH_TOKEN}` } } + { headers: { Authorization: `Basic ${EnvVars.THREE_ROUTE_API_AUTH_TOKEN}` } } ); export const getThreeRouteSwap = threeRouteBuildQueryFn< diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 70a6a53..5bcf340 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -1,6 +1,6 @@ import { BigNumber } from 'bignumber.js'; -import { IPriceHistory } from '../interfaces/price-history'; +import { IPriceHistory } from '../interfaces/price-history.interfaces'; import { blockFinder, EMPTY_BLOCK } from './block-finder'; import DataProvider from './DataProvider'; import fetch from './fetch'; diff --git a/yarn.lock b/yarn.lock index 7388480..aef39d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -218,6 +218,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" @@ -495,6 +500,14 @@ "@types/connect" "*" "@types/node" "*" +"@types/body-parser@^1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/bs58check@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/bs58check/-/bs58check-2.1.0.tgz#7d25a8b88fe7a9e315d2647335ee3c43c8fdb0c0" @@ -1036,6 +1049,24 @@ body-parser@1.20.1: type-is "~1.6.18" unpipe "1.0.0" +body-parser@^1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1162,6 +1193,11 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1229,6 +1265,11 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1371,6 +1412,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -2459,6 +2505,21 @@ internal-slot@^1.0.4: has "^1.0.3" side-channel "^1.0.4" +ioredis@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -2833,11 +2894,21 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" @@ -3479,6 +3550,16 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -3504,6 +3585,18 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -3813,6 +3906,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"