From 6a0faaa697e81582c757f2dd83c8d29b822105a9 Mon Sep 17 00:00:00 2001 From: lendihop Date: Tue, 20 Jun 2023 14:58:50 +0200 Subject: [PATCH 01/15] db setup --- db.json | 52 +++++++++++++++++ package.json | 2 + src/index.ts | 10 ++++ .../{alice-bob => }/alice-bob.interfaces.ts | 0 ...ts => contract-factoctories.interfaces.ts} | 0 src/interfaces/db-data.interface.ts | 5 ++ ...history.ts => price-history.interfaces.ts} | 0 .../{tickers.ts => ticker.interface.ts} | 0 src/notifications/notifications-list.data.ts | 56 +------------------ src/notifications/notifications.utils.ts | 9 ++- src/utils/alice-bob/cancel-alice-bob-order.ts | 2 +- src/utils/alice-bob/create-alice-bob-order.ts | 2 +- .../alice-bob/estimate-alice-bob-output.ts | 2 +- .../alice-bob/get-alice-bob-order-info.ts | 2 +- .../alice-bob/get-alice-bob-pair-info.ts | 2 +- .../alice-bob/get-alice-bob-signature.ts | 2 +- src/utils/tezos.ts | 2 +- src/utils/tokens.ts | 2 +- yarn.lock | 39 ++++++++++++- 19 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 db.json rename src/interfaces/{alice-bob => }/alice-bob.interfaces.ts (100%) rename src/interfaces/{contract-factoctories.ts => contract-factoctories.interfaces.ts} (100%) create mode 100644 src/interfaces/db-data.interface.ts rename src/interfaces/{price-history.ts => price-history.interfaces.ts} (100%) rename src/interfaces/{tickers.ts => ticker.interface.ts} (100%) diff --git a/db.json b/db.json new file mode 100644 index 0000000..7bf8643 --- /dev/null +++ b/db.json @@ -0,0 +1,52 @@ +{ + "notifications": [ + { + "id": 4, + "createdAt": "2022-12-29T16:00:00.000Z", + "type": "News", + "platforms": "Mobile", + "language": "en-US", + "title": "Temple mobile update!", + "description": "Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.", + "content": [ + "In this update:\n", + " • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n", + " • Added fixes related to the latest Taquito update.\n", + " • Other minor improvements.\n", + "\n", + "Check whether your mobile app has the latest version." + ], + "extensionImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/news.svg", + "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/news.svg" + }, + { + "id": 3, + "createdAt": "2022-12-19T15:00:00.000Z", + "type": "News", + "platforms": [ + "Extension", + "Mobile" + ], + "language": "en-US", + "title": "Win a free NFT by Mario Klingemann", + "description": "Use the Quipuswap/Allbridge Tezos cross-chain Bridge to win a limited edition nft by a famous nft artist.", + "content": [ + "To raise awareness about the new cross-chain bridge MadFish and Allbridge teamed up with one of the top digital artists on Tezos - Mario Klingemann (a.k.a @Quasimondo).\n", + "\n", + "How to participate:\n", + " • Bridge at least $200 worth of BUSD (BNB Chain), USDC (Polygon), or ABR (any) to Tezos using Allbridge.\n", + " • Buy any NFT on Teia.art, Rarible.com (tezos nfts only), or Objkt.com.\n", + " • Win one of the 75 unique NFTs by a famous artist.\n", + "\n", + "Participants are automatically registered, and no further user action is required.\n", + "\n", + { + "text": "Read more.", + "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": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/extension-win-nft.svg", + "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/mobile-win-nft.svg" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 64eaf9f..3598dc5 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dotenv": "^9.0.2", "express": "^4.18.2", "firebase-admin": "^10.0.2", + "lowdb": "^1.0.0", "memoizee": "^0.4.15", "pino": "^6.11.2", "pino-http": "^5.5.0", @@ -51,6 +52,7 @@ "eslint-import-resolver-typescript": "^2.7.1", "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", + "@types/lowdb": "^1.0.11", "npm-run-all": "^4.1.5", "prettier": "^2.8.2", "rimraf": "^3.0.2", diff --git a/src/index.ts b/src/index.ts index 9e2eefe..ac1b80c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,12 +3,15 @@ require('./configure'); import cors from 'cors'; import express, { Request, Response } from 'express'; import firebaseAdmin from 'firebase-admin'; +import low from 'lowdb'; +import FileSync from 'lowdb/adapters/FileSync'; import { stdSerializers } from 'pino'; 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 { DbData } from './interfaces/db-data.interface'; import { PlatformType } from './notifications/notification.interface'; import { getNotifications } from './notifications/notifications.utils'; import { getABData } from './utils/ab-test'; @@ -51,6 +54,12 @@ const app = express(); app.use(pinoHttp(PINO_LOGGER)); app.use(cors()); +const adapter = new FileSync('db.json'); +const db = low(adapter); + +const defaultData: DbData = { notifications: [] }; +db.defaults(defaultData).write(); + const dAppsProvider = new SingleQueryDataProvider(15 * 60 * 1000, getDAppsStats); const androidApp = firebaseAdmin.initializeApp( @@ -95,6 +104,7 @@ app.get('/api/notifications', (_req, res) => { try { const { platform, startFromTime } = _req.query; const data = getNotifications( + db, platform === PlatformType.Mobile ? PlatformType.Mobile : PlatformType.Extension, Number(startFromTime) ?? 0 ); 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/db-data.interface.ts b/src/interfaces/db-data.interface.ts new file mode 100644 index 0000000..c0c1bb8 --- /dev/null +++ b/src/interfaces/db-data.interface.ts @@ -0,0 +1,5 @@ +import { Notification } from '../notifications/notification.interface'; + +export interface DbData { + notifications: Notification[]; +} 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/notifications/notifications-list.data.ts b/src/notifications/notifications-list.data.ts index 5838269..8a61fce 100644 --- a/src/notifications/notifications-list.data.ts +++ b/src/notifications/notifications-list.data.ts @@ -6,67 +6,15 @@ 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` + securityNote: `${BANNERS_BUCKET_URL}/extension/security-note.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` + securityNote: `${BANNERS_BUCKET_URL}/mobile/security-note.svg` } }; -export const NOTIFICATIONS_LIST: Notification[] = [ - { - id: 4, - createdAt: '2022-12-29T16:00:00.000Z', - type: NotificationType.News, - platforms: [PlatformType.Mobile], - language: 'en-US', - title: 'Temple mobile update!', - description: - 'Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.', - content: [ - 'In this update:\n', - ' • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n', - ' • Added fixes related to the latest Taquito update.\n', - ' • Other minor improvements.\n', - '\n', - 'Check whether your mobile app has the latest version.' - ], - extensionImageUrl: DEFAULT_BANNER_URLS.extension.news, - mobileImageUrl: DEFAULT_BANNER_URLS.mobile.news - }, - { - id: 3, - createdAt: '2022-12-19T15:00:00.000Z', - type: NotificationType.News, - platforms: [PlatformType.Extension, PlatformType.Mobile], - language: 'en-US', - title: 'Win a free NFT by Mario Klingemann', - description: - 'Use the Quipuswap/Allbridge Tezos cross-chain Bridge to win a limited edition nft by a famous nft artist.', - content: [ - 'To raise awareness about the new cross-chain bridge MadFish and Allbridge teamed up with one of the top digital artists on Tezos - Mario Klingemann (a.k.a @Quasimondo).\n', - '\n', - 'How to participate:\n', - ' • Bridge at least $200 worth of BUSD (BNB Chain), USDC (Polygon), or ABR (any) to Tezos using Allbridge.\n', - ' • Buy any NFT on Teia.art, Rarible.com (tezos nfts only), or Objkt.com.\n', - ' • Win one of the 75 unique NFTs by a famous artist.\n', - '\n', - 'Participants are automatically registered, and no further user action is required.\n', - '\n', - { - text: 'Read more.', - 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 - } -]; - export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { id: 2, diff --git a/src/notifications/notifications.utils.ts b/src/notifications/notifications.utils.ts index b44bc30..ca1d2d5 100644 --- a/src/notifications/notifications.utils.ts +++ b/src/notifications/notifications.utils.ts @@ -1,9 +1,12 @@ +import { LowdbSync } from 'lowdb'; + +import { DbData } from '../interfaces/db-data.interface'; import { PlatformType } from './notification.interface'; -import { MANDATORY_NOTIFICATIONS_LIST, NOTIFICATIONS_LIST } from './notifications-list.data'; +import { MANDATORY_NOTIFICATIONS_LIST } from './notifications-list.data'; -export const getNotifications = (platform: PlatformType, startFromTime: number) => +export const getNotifications = (db: LowdbSync, platform: PlatformType, startFromTime: number) => [ - ...NOTIFICATIONS_LIST.filter(notification => new Date(notification.createdAt).getTime() > startFromTime), + ...db.getState().notifications.filter(notification => new Date(notification.createdAt).getTime() > startFromTime), ...MANDATORY_NOTIFICATIONS_LIST.map(notification => ({ ...notification, createdAt: new Date(startFromTime).toString() 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-signature.ts b/src/utils/alice-bob/get-alice-bob-signature.ts index 9ad6ba4..f60d9ce 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 { AliceBobPayload } from '../../interfaces/alice-bob.interfaces'; export const getAliceBobSignature = (payload?: AliceBobPayload) => { const now = Date.now(); 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/tokens.ts b/src/utils/tokens.ts index fea40bb..46a9d85 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..ddaffd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -593,11 +593,23 @@ dependencies: "@types/node" "*" +"@types/lodash@*": + version "4.14.195" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" + integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== + "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== +"@types/lowdb@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@types/lowdb/-/lowdb-1.0.11.tgz#d8336a635ea0dbd48a7f6f62fb9fccc5ec358ae3" + integrity sha512-h99VMxvTuz+VsXUVCCJo4dsps4vbkXwvU71TpmxDoiBU24bJ0VBygIHgmMm+UPoQIFihmV6euRik4z8J7XDJWg== + dependencies: + "@types/lodash" "*" + "@types/memoizee@^0.4.5": version "0.4.5" resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.5.tgz#cb4e7031decf698c52c4f57c348180b0385aa7da" @@ -2268,6 +2280,11 @@ graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +graceful-fs@^4.1.3: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + gtoken@^5.0.4: version "5.3.2" resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.2.tgz#deb7dc876abe002178e0515e383382ea9446d58f" @@ -2575,7 +2592,7 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-promise@^2.2.2: +is-promise@^2.1.0, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== @@ -2878,7 +2895,7 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -lodash@^4.17.21: +lodash@4, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -2888,6 +2905,17 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +lowdb@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" + integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ== + dependencies: + graceful-fs "^4.1.3" + is-promise "^2.1.0" + lodash "4" + pify "^3.0.0" + steno "^0.4.1" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -3818,6 +3846,13 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +steno@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb" + integrity sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w== + dependencies: + graceful-fs "^4.1.3" + stream-events@^1.0.4, stream-events@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" From f11e425392598bb50f1d6853093a1be17fc75075 Mon Sep 17 00:00:00 2001 From: lendihop Date: Thu, 22 Jun 2023 20:31:20 +0200 Subject: [PATCH 02/15] content parsing, temporary notifications, post in the future feature --- db.json | 3 +- package.json | 4 +- src/index.ts | 54 +++++++++++++++++-- src/interfaces/db-data.interface.ts | 1 + src/notifications/notification.interface.ts | 2 +- src/notifications/notifications.utils.ts | 14 ----- .../utils/clear-expired-notifications.util.ts | 22 ++++++++ .../utils/get-notifications.util.ts | 23 ++++++++ .../utils/get-parsed-content.util.ts | 42 +++++++++++++++ src/notifications/utils/get-platforms.util.ts | 16 ++++++ yarn.lock | 41 ++++++++++++++ 11 files changed, 201 insertions(+), 21 deletions(-) delete mode 100644 src/notifications/notifications.utils.ts create mode 100644 src/notifications/utils/clear-expired-notifications.util.ts create mode 100644 src/notifications/utils/get-notifications.util.ts create mode 100644 src/notifications/utils/get-parsed-content.util.ts create mode 100644 src/notifications/utils/get-platforms.util.ts diff --git a/db.json b/db.json index 7bf8643..2fea5ab 100644 --- a/db.json +++ b/db.json @@ -48,5 +48,6 @@ "extensionImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/extension-win-nft.svg", "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/mobile-win-nft.svg" } - ] + ], + "notificationsExpirationDates": {} } \ No newline at end of file diff --git a/package.json b/package.json index 3598dc5..2ca6457 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@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", @@ -36,9 +37,11 @@ "clean": "rimraf dist/" }, "devDependencies": { + "@types/body-parser": "^1.19.2", "@types/express": "^4.17.17", "@types/express-jwt": "^7.4.2", "@types/express-unless": "^2.0.1", + "@types/lowdb": "^1.0.11", "@types/memoizee": "^0.4.5", "@types/node": "^18.14.6", "@types/pino": "^6.3.8", @@ -52,7 +55,6 @@ "eslint-import-resolver-typescript": "^2.7.1", "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", - "@types/lowdb": "^1.0.11", "npm-run-all": "^4.1.5", "prettier": "^2.8.2", "rimraf": "^3.0.2", diff --git a/src/index.ts b/src/index.ts index ac1b80c..70e022c 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'; @@ -12,8 +13,10 @@ import { getAdvertisingInfo } from './advertising/advertising'; import { MIN_ANDROID_APP_VERSION, MIN_IOS_APP_VERSION } from './config'; import getDAppsStats from './getDAppsStats'; import { DbData } from './interfaces/db-data.interface'; -import { PlatformType } from './notifications/notification.interface'; -import { getNotifications } from './notifications/notifications.utils'; +import { Notification, PlatformType } from './notifications/notification.interface'; +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 { 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'; @@ -21,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 } from './utils/helpers'; import logger from './utils/logger'; import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url'; import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; @@ -53,11 +56,12 @@ const PINO_LOGGER = { const app = express(); app.use(pinoHttp(PINO_LOGGER)); app.use(cors()); +app.use(bodyParser.urlencoded()); const adapter = new FileSync('db.json'); const db = low(adapter); -const defaultData: DbData = { notifications: [] }; +const defaultData: DbData = { notifications: [], notificationsExpirationDates: {} }; db.defaults(defaultData).write(); const dAppsProvider = new SingleQueryDataProvider(15 * 60 * 1000, getDAppsStats); @@ -115,6 +119,48 @@ app.get('/api/notifications', (_req, res) => { } }); +app.post('/api/notifications', (req, res) => { + try { + const { + mobile, + extension, + type, + title, + description, + extensionImageUrl, + mobileImageUrl, + content, + date, + expirationDate + } = req.body; + + const id = Date.now(); + + if (isDefined(expirationDate)) { + db.get('notificationsExpirationDates').set(id, expirationDate).value(); + } + + const newNotification: Notification = { + id, + createdAt: date, + type, + platforms: getPlatforms(mobile, extension), + language: 'en-US', + title, + description, + content: getParsedContent(content), + extensionImageUrl, + mobileImageUrl + }; + + db.get('notifications').push(newNotification).write(); + + 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/db-data.interface.ts b/src/interfaces/db-data.interface.ts index c0c1bb8..41d430d 100644 --- a/src/interfaces/db-data.interface.ts +++ b/src/interfaces/db-data.interface.ts @@ -2,4 +2,5 @@ import { Notification } from '../notifications/notification.interface'; export interface DbData { notifications: Notification[]; + notificationsExpirationDates: Record; } diff --git a/src/notifications/notification.interface.ts b/src/notifications/notification.interface.ts index cdbc9b3..d3a9942 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; } diff --git a/src/notifications/notifications.utils.ts b/src/notifications/notifications.utils.ts deleted file mode 100644 index ca1d2d5..0000000 --- a/src/notifications/notifications.utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { LowdbSync } from 'lowdb'; - -import { DbData } from '../interfaces/db-data.interface'; -import { PlatformType } from './notification.interface'; -import { MANDATORY_NOTIFICATIONS_LIST } from './notifications-list.data'; - -export const getNotifications = (db: LowdbSync, platform: PlatformType, startFromTime: number) => - [ - ...db.getState().notifications.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/clear-expired-notifications.util.ts b/src/notifications/utils/clear-expired-notifications.util.ts new file mode 100644 index 0000000..cd3c72f --- /dev/null +++ b/src/notifications/utils/clear-expired-notifications.util.ts @@ -0,0 +1,22 @@ +import { LowdbSync } from 'lowdb'; + +import { DbData } from '../../interfaces/db-data.interface'; + +export const clearExpiredNotifications = (db: LowdbSync) => { + const { notificationsExpirationDates } = db.getState(); + + const expiredNotificationsIds = Object.keys(notificationsExpirationDates).filter( + key => new Date(notificationsExpirationDates[key]).getTime() < Date.now() + ); + + if (expiredNotificationsIds.length > 0) { + const notifications = db.get('notifications'); + const expirationDates = db.get('notificationsExpirationDates'); + + expiredNotificationsIds.forEach(expiredId => { + notifications.remove({ id: Number(expiredId) }).value(); + expirationDates.unset(expiredId).value(); + }); + db.write(); + } +}; diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts new file mode 100644 index 0000000..daad660 --- /dev/null +++ b/src/notifications/utils/get-notifications.util.ts @@ -0,0 +1,23 @@ +import { LowdbSync } from 'lowdb'; + +import { DbData } from '../../interfaces/db-data.interface'; +import { PlatformType } from '../notification.interface'; +import { MANDATORY_NOTIFICATIONS_LIST } from '../notifications-list.data'; +import { clearExpiredNotifications } from './clear-expired-notifications.util'; + +export const getNotifications = (db: LowdbSync, platform: PlatformType, startFromTime: number) => { + clearExpiredNotifications(db); + const { notifications } = db.getState(); + + return [ + ...notifications.filter(notification => { + const createdAtTimestamp = new Date(notification.createdAt).getTime(); + + return createdAtTimestamp > startFromTime && createdAtTimestamp < Date.now(); + }), + ...MANDATORY_NOTIFICATIONS_LIST.map(notification => ({ + ...notification, + createdAt: new Date(startFromTime).toString() + })) + ].filter(notification => notification.platforms.includes(platform)); +}; 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..ea18ff7 --- /dev/null +++ b/src/notifications/utils/get-parsed-content.util.ts @@ -0,0 +1,42 @@ +import { NotificationLink } from '../notification.interface'; + +export const getParsedContent = (content: string) => + 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(); 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/yarn.lock b/yarn.lock index ddaffd0..903f231 100644 --- a/yarn.lock +++ b/yarn.lock @@ -495,6 +495,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" @@ -1048,6 +1056,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" @@ -1241,6 +1267,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" @@ -3507,6 +3538,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" From c0b20dc4a3eda607814fdff89a2e36f0dbf7b3fb Mon Sep 17 00:00:00 2001 From: lendihop Date: Thu, 22 Jun 2023 22:13:31 +0200 Subject: [PATCH 03/15] fallback image url, refactoring --- db.json | 40 +++++++++---------- src/index.ts | 17 ++++---- src/notifications/default-image-fallbacks.ts | 14 +++++++ ...s => mandatory-notifications-list.data.ts} | 28 ++++--------- .../utils/get-notifications.util.ts | 2 +- .../utils/getImageFallback.util.ts | 24 +++++++++++ src/utils/helpers.ts | 2 + 7 files changed, 78 insertions(+), 49 deletions(-) create mode 100644 src/notifications/default-image-fallbacks.ts rename src/notifications/{notifications-list.data.ts => mandatory-notifications-list.data.ts} (84%) create mode 100644 src/notifications/utils/getImageFallback.util.ts diff --git a/db.json b/db.json index 2fea5ab..bcea74e 100644 --- a/db.json +++ b/db.json @@ -1,24 +1,5 @@ { "notifications": [ - { - "id": 4, - "createdAt": "2022-12-29T16:00:00.000Z", - "type": "News", - "platforms": "Mobile", - "language": "en-US", - "title": "Temple mobile update!", - "description": "Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.", - "content": [ - "In this update:\n", - " • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n", - " • Added fixes related to the latest Taquito update.\n", - " • Other minor improvements.\n", - "\n", - "Check whether your mobile app has the latest version." - ], - "extensionImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/news.svg", - "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/news.svg" - }, { "id": 3, "createdAt": "2022-12-19T15:00:00.000Z", @@ -47,7 +28,26 @@ ], "extensionImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/extension-win-nft.svg", "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/mobile-win-nft.svg" + }, + { + "id": 4, + "createdAt": "2022-12-29T16:00:00.000Z", + "type": "News", + "platforms": "Mobile", + "language": "en-US", + "title": "Temple mobile update!", + "description": "Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.", + "content": [ + "In this update:\n", + " • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n", + " • Added fixes related to the latest Taquito update.\n", + " • Other minor improvements.\n", + "\n", + "Check whether your mobile app has the latest version." + ], + "extensionImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/news.svg", + "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/news.svg" } ], "notificationsExpirationDates": {} -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 70e022c..44692e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,3 @@ -require('./configure'); - import bodyParser from 'body-parser'; import cors from 'cors'; import express, { Request, Response } from 'express'; @@ -17,6 +15,7 @@ import { Notification, PlatformType } from './notifications/notification.interfa 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 { getImageFallback } from './notifications/utils/getImageFallback.util'; 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'; @@ -24,13 +23,15 @@ 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, isDefined } from './utils/helpers'; +import { getExternalApiErrorPayload, isString } from './utils/helpers'; import logger from './utils/logger'; import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url'; import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; import { tezExchangeRateProvider } from './utils/tezos'; import { tokensExchangeRatesProvider } from './utils/tokens'; +require('./configure'); + const PINO_LOGGER = { logger: logger.child({ name: 'web' }), serializers: { @@ -136,7 +137,7 @@ app.post('/api/notifications', (req, res) => { const id = Date.now(); - if (isDefined(expirationDate)) { + if (isString(expirationDate)) { db.get('notificationsExpirationDates').set(id, expirationDate).value(); } @@ -149,11 +150,13 @@ app.post('/api/notifications', (req, res) => { title, description, content: getParsedContent(content), - extensionImageUrl, - mobileImageUrl + extensionImageUrl: isString(extensionImageUrl) + ? extensionImageUrl + : getImageFallback(PlatformType.Extension, type), + mobileImageUrl: isString(mobileImageUrl) ? mobileImageUrl : getImageFallback(PlatformType.Mobile, type) }; - db.get('notifications').push(newNotification).write(); + db.get('notifications').unshift(newNotification).write(); res.status(200).send({ message: 'Notification added successfully' }); } catch (error: any) { diff --git a/src/notifications/default-image-fallbacks.ts b/src/notifications/default-image-fallbacks.ts new file mode 100644 index 0000000..c7f2881 --- /dev/null +++ b/src/notifications/default-image-fallbacks.ts @@ -0,0 +1,14 @@ +const IMAGE_BUCKET_URL = 'https://generic-objects.fra1.digitaloceanspaces.com/notification-icons'; + +export const DEFAULT_IMAGE_URLS = { + extension: { + news: `${IMAGE_BUCKET_URL}/extension/news.svg`, + platformUpdate: `${IMAGE_BUCKET_URL}/extension/platform-update.svg`, + securityNote: `${IMAGE_BUCKET_URL}/extension/security-note.svg` + }, + mobile: { + news: `${IMAGE_BUCKET_URL}/mobile/news.svg`, + platformUpdate: `${IMAGE_BUCKET_URL}/mobile/platform-update.svg`, + securityNote: `${IMAGE_BUCKET_URL}/mobile/security-note.svg` + } +}; diff --git a/src/notifications/notifications-list.data.ts b/src/notifications/mandatory-notifications-list.data.ts similarity index 84% rename from src/notifications/notifications-list.data.ts rename to src/notifications/mandatory-notifications-list.data.ts index 8a61fce..960433f 100644 --- a/src/notifications/notifications-list.data.ts +++ b/src/notifications/mandatory-notifications-list.data.ts @@ -1,20 +1,6 @@ +import { DEFAULT_IMAGE_URLS } from './default-image-fallbacks'; import { Notification, NotificationType, PlatformType } from './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` - }, - mobile: { - news: `${BANNERS_BUCKET_URL}/mobile/news.svg`, - platformUpdate: `${BANNERS_BUCKET_URL}/mobile/platform-update.svg`, - securityNote: `${BANNERS_BUCKET_URL}/mobile/security-note.svg` - } -}; - export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { id: 2, @@ -47,8 +33,8 @@ 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 }, { id: 1, @@ -80,8 +66,8 @@ 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 }, { id: 0, @@ -123,7 +109,7 @@ 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 } ]; diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts index daad660..e3aae15 100644 --- a/src/notifications/utils/get-notifications.util.ts +++ b/src/notifications/utils/get-notifications.util.ts @@ -1,8 +1,8 @@ import { LowdbSync } from 'lowdb'; import { DbData } from '../../interfaces/db-data.interface'; +import { MANDATORY_NOTIFICATIONS_LIST } from '../mandatory-notifications-list.data'; import { PlatformType } from '../notification.interface'; -import { MANDATORY_NOTIFICATIONS_LIST } from '../notifications-list.data'; import { clearExpiredNotifications } from './clear-expired-notifications.util'; export const getNotifications = (db: LowdbSync, platform: PlatformType, startFromTime: number) => { diff --git a/src/notifications/utils/getImageFallback.util.ts b/src/notifications/utils/getImageFallback.util.ts new file mode 100644 index 0000000..2957100 --- /dev/null +++ b/src/notifications/utils/getImageFallback.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/utils/helpers.ts b/src/utils/helpers.ts index c7f612a..bd5193e 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 isString = (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) => { From 1bc486a1780814aac9027ad2e09c3397b4c865b5 Mon Sep 17 00:00:00 2001 From: lendihop Date: Sat, 1 Jul 2023 00:27:59 +0200 Subject: [PATCH 04/15] redis setup --- .env.dist | 1 + db.json | 53 ---------- package.json | 3 +- redis.conf | 2 + src/config.ts | 1 + src/index.ts | 38 +++----- src/interfaces/db-data.interface.ts | 6 -- src/notifications/default-image-fallbacks.ts | 16 ++-- .../default-notifications-list.data.ts | 53 ++++++++++ src/notifications/notification.interface.ts | 1 + .../utils/addDefaultNotifications.ts | 13 +++ .../utils/clear-expired-notifications.util.ts | 27 +++--- .../utils/get-notifications.util.ts | 17 ++-- yarn.lock | 96 ++++++++++++------- 14 files changed, 176 insertions(+), 151 deletions(-) delete mode 100644 db.json create mode 100644 redis.conf delete mode 100644 src/interfaces/db-data.interface.ts create mode 100644 src/notifications/default-notifications-list.data.ts create mode 100644 src/notifications/utils/addDefaultNotifications.ts diff --git a/.env.dist b/.env.dist index d46d9b9..e13d893 100644 --- a/.env.dist +++ b/.env.dist @@ -7,3 +7,4 @@ ALICE_BOB_PUBLIC_KEY= ALICE_BOB_PRIVATE_KEY= THREE_ROUTE_API_URL= THREE_ROUTE_API_AUTH_TOKEN= +REDIS_URL=redis://localhost:6379 diff --git a/db.json b/db.json deleted file mode 100644 index bcea74e..0000000 --- a/db.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "notifications": [ - { - "id": 3, - "createdAt": "2022-12-19T15:00:00.000Z", - "type": "News", - "platforms": [ - "Extension", - "Mobile" - ], - "language": "en-US", - "title": "Win a free NFT by Mario Klingemann", - "description": "Use the Quipuswap/Allbridge Tezos cross-chain Bridge to win a limited edition nft by a famous nft artist.", - "content": [ - "To raise awareness about the new cross-chain bridge MadFish and Allbridge teamed up with one of the top digital artists on Tezos - Mario Klingemann (a.k.a @Quasimondo).\n", - "\n", - "How to participate:\n", - " • Bridge at least $200 worth of BUSD (BNB Chain), USDC (Polygon), or ABR (any) to Tezos using Allbridge.\n", - " • Buy any NFT on Teia.art, Rarible.com (tezos nfts only), or Objkt.com.\n", - " • Win one of the 75 unique NFTs by a famous artist.\n", - "\n", - "Participants are automatically registered, and no further user action is required.\n", - "\n", - { - "text": "Read more.", - "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": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/extension-win-nft.svg", - "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/mobile-win-nft.svg" - }, - { - "id": 4, - "createdAt": "2022-12-29T16:00:00.000Z", - "type": "News", - "platforms": "Mobile", - "language": "en-US", - "title": "Temple mobile update!", - "description": "Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.", - "content": [ - "In this update:\n", - " • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n", - " • Added fixes related to the latest Taquito update.\n", - " • Other minor improvements.\n", - "\n", - "Check whether your mobile app has the latest version." - ], - "extensionImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/extension/news.svg", - "mobileImageUrl": "https://generic-objects.fra1.digitaloceanspaces.com/notification-icons/mobile/news.svg" - } - ], - "notificationsExpirationDates": {} -} diff --git a/package.json b/package.json index 2ca6457..a06ab4c 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,12 @@ "dotenv": "^9.0.2", "express": "^4.18.2", "firebase-admin": "^10.0.2", - "lowdb": "^1.0.0", "memoizee": "^0.4.15", "pino": "^6.11.2", "pino-http": "^5.5.0", "pino-pretty": "^4.7.1", "qs": "^6.10.3", + "ioredis": "^5.3.2", "semaphore": "^1.1.0" }, "scripts": { @@ -41,7 +41,6 @@ "@types/express": "^4.17.17", "@types/express-jwt": "^7.4.2", "@types/express-unless": "^2.0.1", - "@types/lowdb": "^1.0.11", "@types/memoizee": "^0.4.5", "@types/node": "^18.14.6", "@types/pino": "^6.3.8", diff --git a/redis.conf b/redis.conf new file mode 100644 index 0000000..c697778 --- /dev/null +++ b/redis.conf @@ -0,0 +1,2 @@ +appendonly no +save "" diff --git a/src/config.ts b/src/config.ts index 85f6c36..aa21433 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,6 +9,7 @@ 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 REDIS_URL = getEnv('REDIS_URL'); const variablesToAssert = [ { name: 'MOONPAY_SECRET_KEY', value: MOONPAY_SECRET_KEY }, diff --git a/src/index.ts b/src/index.ts index 44692e4..9ff5622 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,16 @@ +require('./configure'); + import bodyParser from 'body-parser'; import cors from 'cors'; import express, { Request, Response } from 'express'; import firebaseAdmin from 'firebase-admin'; -import low from 'lowdb'; -import FileSync from 'lowdb/adapters/FileSync'; +import { Redis } from 'ioredis'; import { stdSerializers } from 'pino'; import pinoHttp from 'pino-http'; import { getAdvertisingInfo } from './advertising/advertising'; -import { MIN_ANDROID_APP_VERSION, MIN_IOS_APP_VERSION } from './config'; +import { MIN_ANDROID_APP_VERSION, MIN_IOS_APP_VERSION, REDIS_URL } from './config'; import getDAppsStats from './getDAppsStats'; -import { DbData } from './interfaces/db-data.interface'; import { Notification, PlatformType } from './notifications/notification.interface'; import { getNotifications } from './notifications/utils/get-notifications.util'; import { getParsedContent } from './notifications/utils/get-parsed-content.util'; @@ -30,8 +30,6 @@ import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; import { tezExchangeRateProvider } from './utils/tezos'; import { tokensExchangeRatesProvider } from './utils/tokens'; -require('./configure'); - const PINO_LOGGER = { logger: logger.child({ name: 'web' }), serializers: { @@ -59,11 +57,8 @@ app.use(pinoHttp(PINO_LOGGER)); app.use(cors()); app.use(bodyParser.urlencoded()); -const adapter = new FileSync('db.json'); -const db = low(adapter); - -const defaultData: DbData = { notifications: [], notificationsExpirationDates: {} }; -db.defaults(defaultData).write(); +const redisClient = new Redis(REDIS_URL); +redisClient.on('error', err => console.log('Redis Client Error', err)); const dAppsProvider = new SingleQueryDataProvider(15 * 60 * 1000, getDAppsStats); @@ -105,11 +100,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( - db, + const data = await getNotifications( + redisClient, platform === PlatformType.Mobile ? PlatformType.Mobile : PlatformType.Extension, Number(startFromTime) ?? 0 ); @@ -120,7 +115,7 @@ app.get('/api/notifications', (_req, res) => { } }); -app.post('/api/notifications', (req, res) => { +app.post('/api/notifications', async (req, res) => { try { const { mobile, @@ -135,14 +130,8 @@ app.post('/api/notifications', (req, res) => { expirationDate } = req.body; - const id = Date.now(); - - if (isString(expirationDate)) { - db.get('notificationsExpirationDates').set(id, expirationDate).value(); - } - const newNotification: Notification = { - id, + id: Date.now(), createdAt: date, type, platforms: getPlatforms(mobile, extension), @@ -153,10 +142,11 @@ app.post('/api/notifications', (req, res) => { extensionImageUrl: isString(extensionImageUrl) ? extensionImageUrl : getImageFallback(PlatformType.Extension, type), - mobileImageUrl: isString(mobileImageUrl) ? mobileImageUrl : getImageFallback(PlatformType.Mobile, type) + mobileImageUrl: isString(mobileImageUrl) ? mobileImageUrl : getImageFallback(PlatformType.Mobile, type), + expirationDate }; - db.get('notifications').unshift(newNotification).write(); + await redisClient.lpush('notifications', JSON.stringify(newNotification)); res.status(200).send({ message: 'Notification added successfully' }); } catch (error: any) { diff --git a/src/interfaces/db-data.interface.ts b/src/interfaces/db-data.interface.ts deleted file mode 100644 index 41d430d..0000000 --- a/src/interfaces/db-data.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Notification } from '../notifications/notification.interface'; - -export interface DbData { - notifications: Notification[]; - notificationsExpirationDates: Record; -} diff --git a/src/notifications/default-image-fallbacks.ts b/src/notifications/default-image-fallbacks.ts index c7f2881..a725041 100644 --- a/src/notifications/default-image-fallbacks.ts +++ b/src/notifications/default-image-fallbacks.ts @@ -1,14 +1,16 @@ -const IMAGE_BUCKET_URL = 'https://generic-objects.fra1.digitaloceanspaces.com/notification-icons'; +const IMAGES_BUCKET_URL = 'https://generic-objects.fra1.digitaloceanspaces.com/notification-icons'; export const DEFAULT_IMAGE_URLS = { extension: { - news: `${IMAGE_BUCKET_URL}/extension/news.svg`, - platformUpdate: `${IMAGE_BUCKET_URL}/extension/platform-update.svg`, - securityNote: `${IMAGE_BUCKET_URL}/extension/security-note.svg` + 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: `${IMAGE_BUCKET_URL}/mobile/news.svg`, - platformUpdate: `${IMAGE_BUCKET_URL}/mobile/platform-update.svg`, - securityNote: `${IMAGE_BUCKET_URL}/mobile/security-note.svg` + 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/default-notifications-list.data.ts b/src/notifications/default-notifications-list.data.ts new file mode 100644 index 0000000..d764398 --- /dev/null +++ b/src/notifications/default-notifications-list.data.ts @@ -0,0 +1,53 @@ +import { DEFAULT_IMAGE_URLS } from './default-image-fallbacks'; +import { Notification, NotificationType, PlatformType } from './notification.interface'; + +//TODO: delete after deploy +export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ + { + id: 4, + createdAt: '2022-12-29T16:00:00.000Z', + type: NotificationType.News, + platforms: [PlatformType.Mobile], + language: 'en-US', + title: 'Temple mobile update!', + description: + 'Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.', + content: [ + 'In this update:\n', + ' • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n', + ' • Added fixes related to the latest Taquito update.\n', + ' • Other minor improvements.\n', + '\n', + 'Check whether your mobile app has the latest version.' + ], + extensionImageUrl: DEFAULT_IMAGE_URLS.extension.news, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news + }, + { + id: 3, + createdAt: '2022-12-19T15:00:00.000Z', + type: NotificationType.News, + platforms: [PlatformType.Extension, PlatformType.Mobile], + language: 'en-US', + title: 'Win a free NFT by Mario Klingemann', + description: + 'Use the Quipuswap/Allbridge Tezos cross-chain Bridge to win a limited edition nft by a famous nft artist.', + content: [ + 'To raise awareness about the new cross-chain bridge MadFish and Allbridge teamed up with one of the top digital artists on Tezos - Mario Klingemann (a.k.a @Quasimondo).\n', + '\n', + 'How to participate:\n', + ' • Bridge at least $200 worth of BUSD (BNB Chain), USDC (Polygon), or ABR (any) to Tezos using Allbridge.\n', + ' • Buy any NFT on Teia.art, Rarible.com (tezos nfts only), or Objkt.com.\n', + ' • Win one of the 75 unique NFTs by a famous artist.\n', + '\n', + 'Participants are automatically registered, and no further user action is required.\n', + '\n', + { + text: 'Read more.', + 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_IMAGE_URLS.extension.winNft, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.winNft + } +]; diff --git a/src/notifications/notification.interface.ts b/src/notifications/notification.interface.ts index d3a9942..495cbd5 100644 --- a/src/notifications/notification.interface.ts +++ b/src/notifications/notification.interface.ts @@ -26,4 +26,5 @@ export interface Notification { extensionImageUrl: string; mobileImageUrl: string; sourceUrl?: string; + expirationDate?: string; } diff --git a/src/notifications/utils/addDefaultNotifications.ts b/src/notifications/utils/addDefaultNotifications.ts new file mode 100644 index 0000000..1b37d1a --- /dev/null +++ b/src/notifications/utils/addDefaultNotifications.ts @@ -0,0 +1,13 @@ +import { Redis } from 'ioredis'; + +import { DEFAULT_NOTIFICATIONS_LIST } from '../default-notifications-list.data'; + +//TODO: delete after deploy +export const addDefaultNotifications = async (client: Redis) => { + const data = await client.lrange('notifications', 0, -1); + if (data.length === 0) { + for (let i = 0; i < DEFAULT_NOTIFICATIONS_LIST.length; i++) { + await client.rpush('notifications', JSON.stringify(DEFAULT_NOTIFICATIONS_LIST[i])); + } + } +}; diff --git a/src/notifications/utils/clear-expired-notifications.util.ts b/src/notifications/utils/clear-expired-notifications.util.ts index cd3c72f..0d44a06 100644 --- a/src/notifications/utils/clear-expired-notifications.util.ts +++ b/src/notifications/utils/clear-expired-notifications.util.ts @@ -1,22 +1,17 @@ -import { LowdbSync } from 'lowdb'; +import { Redis } from 'ioredis'; -import { DbData } from '../../interfaces/db-data.interface'; +import { isDefined } from '../../utils/helpers'; +import { Notification } from '../notification.interface'; -export const clearExpiredNotifications = (db: LowdbSync) => { - const { notificationsExpirationDates } = db.getState(); - - const expiredNotificationsIds = Object.keys(notificationsExpirationDates).filter( - key => new Date(notificationsExpirationDates[key]).getTime() < Date.now() +export const clearExpiredNotifications = async (client: Redis, notifications: Notification[]) => { + const now = Date.now(); + const expiredNotifications = notifications.filter( + ({ expirationDate }) => isDefined(expirationDate) && new Date(expirationDate).getTime() < now ); - if (expiredNotificationsIds.length > 0) { - const notifications = db.get('notifications'); - const expirationDates = db.get('notificationsExpirationDates'); - - expiredNotificationsIds.forEach(expiredId => { - notifications.remove({ id: Number(expiredId) }).value(); - expirationDates.unset(expiredId).value(); - }); - db.write(); + if (expiredNotifications.length > 0) { + for (let i = 0; i < expiredNotifications.length; i++) { + await client.lrem('notifications', 1, JSON.stringify(expiredNotifications[i])); + } } }; diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts index e3aae15..c1128ee 100644 --- a/src/notifications/utils/get-notifications.util.ts +++ b/src/notifications/utils/get-notifications.util.ts @@ -1,13 +1,18 @@ -import { LowdbSync } from 'lowdb'; +import { Redis } from 'ioredis'; +import memoizee from 'memoizee'; -import { DbData } from '../../interfaces/db-data.interface'; import { MANDATORY_NOTIFICATIONS_LIST } from '../mandatory-notifications-list.data'; -import { PlatformType } from '../notification.interface'; +import { Notification, PlatformType } from '../notification.interface'; +import { addDefaultNotifications } from './addDefaultNotifications'; import { clearExpiredNotifications } from './clear-expired-notifications.util'; -export const getNotifications = (db: LowdbSync, platform: PlatformType, startFromTime: number) => { - clearExpiredNotifications(db); - const { notifications } = db.getState(); +export const getNotifications = async (client: Redis, platform: PlatformType, startFromTime: number) => { + await addDefaultNotifications(client); + + const data = await client.lrange('notifications', 0, -1); + const notifications: Notification[] = data.map(item => JSON.parse(item)); + + await clearExpiredNotifications(client, notifications); return [ ...notifications.filter(notification => { diff --git a/yarn.lock b/yarn.lock index 903f231..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" @@ -601,23 +606,11 @@ dependencies: "@types/node" "*" -"@types/lodash@*": - version "4.14.195" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" - integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== - "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== -"@types/lowdb@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@types/lowdb/-/lowdb-1.0.11.tgz#d8336a635ea0dbd48a7f6f62fb9fccc5ec358ae3" - integrity sha512-h99VMxvTuz+VsXUVCCJo4dsps4vbkXwvU71TpmxDoiBU24bJ0VBygIHgmMm+UPoQIFihmV6euRik4z8J7XDJWg== - dependencies: - "@types/lodash" "*" - "@types/memoizee@^0.4.5": version "0.4.5" resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.5.tgz#cb4e7031decf698c52c4f57c348180b0385aa7da" @@ -1200,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" @@ -1414,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" @@ -2311,11 +2314,6 @@ graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -graceful-fs@^4.1.3: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - gtoken@^5.0.4: version "5.3.2" resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.2.tgz#deb7dc876abe002178e0515e383382ea9446d58f" @@ -2507,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" @@ -2623,7 +2636,7 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-promise@^2.1.0, is-promise@^2.2.2: +is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== @@ -2881,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" @@ -2926,7 +2949,7 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -lodash@4, lodash@^4.17.21: +lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -2936,17 +2959,6 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -lowdb@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" - integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ== - dependencies: - graceful-fs "^4.1.3" - is-promise "^2.1.0" - lodash "4" - pify "^3.0.0" - steno "^0.4.1" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -3573,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" @@ -3882,18 +3906,16 @@ 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" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -steno@^0.4.1: - version "0.4.4" - resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb" - integrity sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w== - dependencies: - graceful-fs "^4.1.3" - stream-events@^1.0.4, stream-events@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" From 298f15908715ccfa8d33c2ddbb035c5571c13795 Mon Sep 17 00:00:00 2001 From: lendihop Date: Sat, 1 Jul 2023 00:30:49 +0200 Subject: [PATCH 05/15] -import --- src/notifications/utils/get-notifications.util.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts index c1128ee..5636269 100644 --- a/src/notifications/utils/get-notifications.util.ts +++ b/src/notifications/utils/get-notifications.util.ts @@ -1,5 +1,4 @@ import { Redis } from 'ioredis'; -import memoizee from 'memoizee'; import { MANDATORY_NOTIFICATIONS_LIST } from '../mandatory-notifications-list.data'; import { Notification, PlatformType } from '../notification.interface'; From 5420c66e75ec9ce546ab5607c2f02063fbd4487c Mon Sep 17 00:00:00 2001 From: lendihop Date: Mon, 3 Jul 2023 01:54:09 +0200 Subject: [PATCH 06/15] adding mandatory notifications --- .gitignore | 3 + redis.conf | 2 - src/index.ts | 10 +++- .../default-notifications-list.data.ts | 53 ---------------- ...data.ts => existing-notifications.data.ts} | 60 ++++++++++++++++++- src/notifications/notification.interface.ts | 1 + .../utils/add-existing-notifications-to-db.ts | 15 +++++ .../utils/addDefaultNotifications.ts | 13 ---- .../utils/clear-expired-notifications.util.ts | 17 ------ .../utils/get-notifications.util.ts | 39 +++++++----- src/notifications/utils/sort-notifications.ts | 17 ++++++ 11 files changed, 123 insertions(+), 107 deletions(-) delete mode 100644 redis.conf delete mode 100644 src/notifications/default-notifications-list.data.ts rename src/notifications/{mandatory-notifications-list.data.ts => existing-notifications.data.ts} (68%) create mode 100644 src/notifications/utils/add-existing-notifications-to-db.ts delete mode 100644 src/notifications/utils/addDefaultNotifications.ts delete mode 100644 src/notifications/utils/clear-expired-notifications.util.ts create mode 100644 src/notifications/utils/sort-notifications.ts diff --git a/.gitignore b/.gitignore index 9fb2dbd..150d69e 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ dist # IDE .idea + +# Redis +dump.rdb diff --git a/redis.conf b/redis.conf deleted file mode 100644 index c697778..0000000 --- a/redis.conf +++ /dev/null @@ -1,2 +0,0 @@ -appendonly no -save "" diff --git a/src/index.ts b/src/index.ts index 9ff5622..b64f324 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ 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 { getImageFallback } from './notifications/utils/getImageFallback.util'; +import { sortNotifications } from './notifications/utils/sort-notifications'; 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'; @@ -23,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, isString } from './utils/helpers'; +import { getExternalApiErrorPayload, isDefined, isString } from './utils/helpers'; import logger from './utils/logger'; import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url'; import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; @@ -127,7 +128,8 @@ app.post('/api/notifications', async (req, res) => { mobileImageUrl, content, date, - expirationDate + expirationDate, + isMandatory } = req.body; const newNotification: Notification = { @@ -143,10 +145,12 @@ app.post('/api/notifications', async (req, res) => { ? extensionImageUrl : getImageFallback(PlatformType.Extension, type), mobileImageUrl: isString(mobileImageUrl) ? mobileImageUrl : getImageFallback(PlatformType.Mobile, type), - expirationDate + expirationDate, + isMandatory: isDefined(isMandatory) }; await redisClient.lpush('notifications', JSON.stringify(newNotification)); + await sortNotifications(redisClient); res.status(200).send({ message: 'Notification added successfully' }); } catch (error: any) { diff --git a/src/notifications/default-notifications-list.data.ts b/src/notifications/default-notifications-list.data.ts deleted file mode 100644 index d764398..0000000 --- a/src/notifications/default-notifications-list.data.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { DEFAULT_IMAGE_URLS } from './default-image-fallbacks'; -import { Notification, NotificationType, PlatformType } from './notification.interface'; - -//TODO: delete after deploy -export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ - { - id: 4, - createdAt: '2022-12-29T16:00:00.000Z', - type: NotificationType.News, - platforms: [PlatformType.Mobile], - language: 'en-US', - title: 'Temple mobile update!', - description: - 'Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.', - content: [ - 'In this update:\n', - ' • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n', - ' • Added fixes related to the latest Taquito update.\n', - ' • Other minor improvements.\n', - '\n', - 'Check whether your mobile app has the latest version.' - ], - extensionImageUrl: DEFAULT_IMAGE_URLS.extension.news, - mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news - }, - { - id: 3, - createdAt: '2022-12-19T15:00:00.000Z', - type: NotificationType.News, - platforms: [PlatformType.Extension, PlatformType.Mobile], - language: 'en-US', - title: 'Win a free NFT by Mario Klingemann', - description: - 'Use the Quipuswap/Allbridge Tezos cross-chain Bridge to win a limited edition nft by a famous nft artist.', - content: [ - 'To raise awareness about the new cross-chain bridge MadFish and Allbridge teamed up with one of the top digital artists on Tezos - Mario Klingemann (a.k.a @Quasimondo).\n', - '\n', - 'How to participate:\n', - ' • Bridge at least $200 worth of BUSD (BNB Chain), USDC (Polygon), or ABR (any) to Tezos using Allbridge.\n', - ' • Buy any NFT on Teia.art, Rarible.com (tezos nfts only), or Objkt.com.\n', - ' • Win one of the 75 unique NFTs by a famous artist.\n', - '\n', - 'Participants are automatically registered, and no further user action is required.\n', - '\n', - { - text: 'Read more.', - 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_IMAGE_URLS.extension.winNft, - mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.winNft - } -]; diff --git a/src/notifications/mandatory-notifications-list.data.ts b/src/notifications/existing-notifications.data.ts similarity index 68% rename from src/notifications/mandatory-notifications-list.data.ts rename to src/notifications/existing-notifications.data.ts index 960433f..6a274c6 100644 --- a/src/notifications/mandatory-notifications-list.data.ts +++ b/src/notifications/existing-notifications.data.ts @@ -1,6 +1,57 @@ import { DEFAULT_IMAGE_URLS } from './default-image-fallbacks'; import { Notification, NotificationType, PlatformType } from './notification.interface'; +//TODO: delete after deploy +export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ + { + id: 4, + createdAt: '2022-12-29T16:00:00.000Z', + type: NotificationType.News, + platforms: [PlatformType.Mobile], + language: 'en-US', + title: 'Temple mobile update!', + description: + 'Don’t forget to update your Mobile app to the latest version. Click “Details” to learn what’s in this patch.', + content: [ + 'In this update:\n', + ' • The new market feature was released. Track your favorite Tezos coins directly in your wallet. Add to favorites by clicking the star icon, or instantly buy tokens you like. To start using all features - choose a token and swipe it.\n', + ' • Added fixes related to the latest Taquito update.\n', + ' • Other minor improvements.\n', + '\n', + 'Check whether your mobile app has the latest version.' + ], + extensionImageUrl: DEFAULT_IMAGE_URLS.extension.news, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news + }, + { + id: 3, + createdAt: '2022-12-19T15:00:00.000Z', + type: NotificationType.News, + platforms: [PlatformType.Extension, PlatformType.Mobile], + language: 'en-US', + title: 'Win a free NFT by Mario Klingemann', + description: + 'Use the Quipuswap/Allbridge Tezos cross-chain Bridge to win a limited edition nft by a famous nft artist.', + content: [ + 'To raise awareness about the new cross-chain bridge MadFish and Allbridge teamed up with one of the top digital artists on Tezos - Mario Klingemann (a.k.a @Quasimondo).\n', + '\n', + 'How to participate:\n', + ' • Bridge at least $200 worth of BUSD (BNB Chain), USDC (Polygon), or ABR (any) to Tezos using Allbridge.\n', + ' • Buy any NFT on Teia.art, Rarible.com (tezos nfts only), or Objkt.com.\n', + ' • Win one of the 75 unique NFTs by a famous artist.\n', + '\n', + 'Participants are automatically registered, and no further user action is required.\n', + '\n', + { + text: 'Read more.', + 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_IMAGE_URLS.extension.winNft, + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.winNft + } +]; + export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { id: 2, @@ -34,7 +85,8 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ '.' ], extensionImageUrl: DEFAULT_IMAGE_URLS.extension.securityNote, - mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.securityNote + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.securityNote, + isMandatory: true }, { id: 1, @@ -67,7 +119,8 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ '.' ], extensionImageUrl: DEFAULT_IMAGE_URLS.extension.securityNote, - mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.securityNote + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.securityNote, + isMandatory: true }, { id: 0, @@ -110,6 +163,7 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ '. We’re happy to have you!\n' ], extensionImageUrl: DEFAULT_IMAGE_URLS.extension.news, - mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news + mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news, + isMandatory: true } ]; diff --git a/src/notifications/notification.interface.ts b/src/notifications/notification.interface.ts index 495cbd5..d4968aa 100644 --- a/src/notifications/notification.interface.ts +++ b/src/notifications/notification.interface.ts @@ -27,4 +27,5 @@ export interface Notification { mobileImageUrl: string; sourceUrl?: string; expirationDate?: string; + isMandatory?: boolean; } diff --git a/src/notifications/utils/add-existing-notifications-to-db.ts b/src/notifications/utils/add-existing-notifications-to-db.ts new file mode 100644 index 0000000..adf2722 --- /dev/null +++ b/src/notifications/utils/add-existing-notifications-to-db.ts @@ -0,0 +1,15 @@ +import { Redis } from 'ioredis'; + +import { DEFAULT_NOTIFICATIONS_LIST, MANDATORY_NOTIFICATIONS_LIST } from '../existing-notifications.data'; + +//TODO: delete after deploy +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/src/notifications/utils/addDefaultNotifications.ts b/src/notifications/utils/addDefaultNotifications.ts deleted file mode 100644 index 1b37d1a..0000000 --- a/src/notifications/utils/addDefaultNotifications.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Redis } from 'ioredis'; - -import { DEFAULT_NOTIFICATIONS_LIST } from '../default-notifications-list.data'; - -//TODO: delete after deploy -export const addDefaultNotifications = async (client: Redis) => { - const data = await client.lrange('notifications', 0, -1); - if (data.length === 0) { - for (let i = 0; i < DEFAULT_NOTIFICATIONS_LIST.length; i++) { - await client.rpush('notifications', JSON.stringify(DEFAULT_NOTIFICATIONS_LIST[i])); - } - } -}; diff --git a/src/notifications/utils/clear-expired-notifications.util.ts b/src/notifications/utils/clear-expired-notifications.util.ts deleted file mode 100644 index 0d44a06..0000000 --- a/src/notifications/utils/clear-expired-notifications.util.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Redis } from 'ioredis'; - -import { isDefined } from '../../utils/helpers'; -import { Notification } from '../notification.interface'; - -export const clearExpiredNotifications = async (client: Redis, notifications: Notification[]) => { - const now = Date.now(); - const expiredNotifications = notifications.filter( - ({ expirationDate }) => isDefined(expirationDate) && new Date(expirationDate).getTime() < now - ); - - if (expiredNotifications.length > 0) { - for (let i = 0; i < expiredNotifications.length; i++) { - await client.lrem('notifications', 1, JSON.stringify(expiredNotifications[i])); - } - } -}; diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts index 5636269..1394e1e 100644 --- a/src/notifications/utils/get-notifications.util.ts +++ b/src/notifications/utils/get-notifications.util.ts @@ -1,27 +1,34 @@ import { Redis } from 'ioredis'; -import { MANDATORY_NOTIFICATIONS_LIST } from '../mandatory-notifications-list.data'; +import { isString } from '../../utils/helpers'; import { Notification, PlatformType } from '../notification.interface'; -import { addDefaultNotifications } from './addDefaultNotifications'; -import { clearExpiredNotifications } from './clear-expired-notifications.util'; +import { addExistingNotificationsToDb } from './add-existing-notifications-to-db'; export const getNotifications = async (client: Redis, platform: PlatformType, startFromTime: number) => { - await addDefaultNotifications(client); + await addExistingNotificationsToDb(client); const data = await client.lrange('notifications', 0, -1); - const notifications: Notification[] = data.map(item => JSON.parse(item)); - await clearExpiredNotifications(client, notifications); + const now = Date.now(); + const result: Notification[] = []; - return [ - ...notifications.filter(notification => { - const createdAtTimestamp = new Date(notification.createdAt).getTime(); + for (let i = 0; i < data.length; i++) { + const notification: Notification = JSON.parse(data[i]); + const { isMandatory, createdAt, platforms, expirationDate } = notification; + const createdAtTimestamp = new Date(createdAt).getTime(); - return createdAtTimestamp > startFromTime && createdAtTimestamp < Date.now(); - }), - ...MANDATORY_NOTIFICATIONS_LIST.map(notification => ({ - ...notification, - createdAt: new Date(startFromTime).toString() - })) - ].filter(notification => notification.platforms.includes(platform)); + if (isString(expirationDate) && new Date(expirationDate).getTime() < now) { + await client.lrem('notifications', 1, JSON.stringify(notification)); + continue; + } + + if ( + platforms.includes(platform) && + (isMandatory === true || (createdAtTimestamp > startFromTime && createdAtTimestamp < now)) + ) { + result.push(notification); + } + } + + return result; }; diff --git a/src/notifications/utils/sort-notifications.ts b/src/notifications/utils/sort-notifications.ts new file mode 100644 index 0000000..d91bb3f --- /dev/null +++ b/src/notifications/utils/sort-notifications.ts @@ -0,0 +1,17 @@ +import { Redis } from 'ioredis'; + +import { Notification } from '../notification.interface'; + +export const sortNotifications = async (client: Redis) => { + const data = await client.lrange('notifications', 0, -1); + const notifications: Notification[] = data.map(item => JSON.parse(item)); + const sortedNotifications = notifications.sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); + + await client.del('notifications'); + + for (let i = 0; i < sortedNotifications.length; i++) { + await client.rpush('notifications', JSON.stringify(sortedNotifications[i])); + } +}; From f876597010b91c3928e4132d4918c44634f095a6 Mon Sep 17 00:00:00 2001 From: lendihop Date: Wed, 5 Jul 2023 22:55:09 +0200 Subject: [PATCH 07/15] auth --- .env.dist | 4 ++- package.json | 2 +- src/config.ts | 7 +++- src/index.ts | 18 +++++----- src/middlewares/basic-auth.middleware.ts | 25 +++++++++++++ .../existing-notifications.data.ts | 10 +++--- src/notifications/notification.interface.ts | 2 +- ...ack.util.ts => get-image-fallback.util.ts} | 0 .../utils/get-notifications.util.ts | 4 +-- .../utils/get-parsed-content.util.ts | 27 +++++++++++++- src/utils/helpers.ts | 2 +- yarn.lock | 35 ++++++++++++++++++- 12 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 src/middlewares/basic-auth.middleware.ts rename src/notifications/utils/{getImageFallback.util.ts => get-image-fallback.util.ts} (100%) diff --git a/.env.dist b/.env.dist index e13d893..2b50f2b 100644 --- a/.env.dist +++ b/.env.dist @@ -7,4 +7,6 @@ ALICE_BOB_PUBLIC_KEY= ALICE_BOB_PRIVATE_KEY= THREE_ROUTE_API_URL= THREE_ROUTE_API_AUTH_TOKEN= -REDIS_URL=redis://localhost:6379 +REDIS_URL= +ADD_NOTIFICATION_USERNAME= +ADD_NOTIFICATION_PASSWORD= diff --git a/package.json b/package.json index a06ab4c..0c3dc95 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,12 @@ "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", "pino-pretty": "^4.7.1", "qs": "^6.10.3", - "ioredis": "^5.3.2", "semaphore": "^1.1.0" }, "scripts": { diff --git a/src/config.ts b/src/config.ts index aa21433..e1db954 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,13 +10,18 @@ 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 REDIS_URL = getEnv('REDIS_URL'); +export const ADD_NOTIFICATION_USERNAME = getEnv('ADD_NOTIFICATION_USERNAME'); +export const 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 } + { name: 'THREE_ROUTE_API_AUTH_TOKEN', value: THREE_ROUTE_API_AUTH_TOKEN }, + { name: 'REDIS_URL', value: REDIS_URL }, + { name: 'ADD_NOTIFICATION_USERNAME', value: ADD_NOTIFICATION_USERNAME }, + { name: 'ADD_NOTIFICATION_PASSWORD', value: ADD_NOTIFICATION_PASSWORD } ]; variablesToAssert.forEach(({ name, value }) => { if (!isDefined(value)) { diff --git a/src/index.ts b/src/index.ts index b64f324..077d02a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ require('./configure'); import bodyParser from 'body-parser'; import cors from 'cors'; +import { randomUUID } from 'crypto'; import express, { Request, Response } from 'express'; import firebaseAdmin from 'firebase-admin'; import { Redis } from 'ioredis'; @@ -11,11 +12,12 @@ import pinoHttp from 'pino-http'; import { getAdvertisingInfo } from './advertising/advertising'; import { MIN_ANDROID_APP_VERSION, MIN_IOS_APP_VERSION, REDIS_URL } from './config'; import getDAppsStats from './getDAppsStats'; +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 { getImageFallback } from './notifications/utils/getImageFallback.util'; import { sortNotifications } from './notifications/utils/sort-notifications'; import { getABData } from './utils/ab-test'; import { cancelAliceBobOrder } from './utils/alice-bob/cancel-alice-bob-order'; @@ -24,7 +26,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, isDefined, isString } 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'; @@ -56,10 +58,10 @@ const PINO_LOGGER = { const app = express(); app.use(pinoHttp(PINO_LOGGER)); app.use(cors()); -app.use(bodyParser.urlencoded()); +app.use(bodyParser.json()); const redisClient = new Redis(REDIS_URL); -redisClient.on('error', err => console.log('Redis Client Error', err)); +redisClient.on('error', err => logger.error(err)); const dAppsProvider = new SingleQueryDataProvider(15 * 60 * 1000, getDAppsStats); @@ -116,7 +118,7 @@ app.get('/api/notifications', async (_req, res) => { } }); -app.post('/api/notifications', async (req, res) => { +app.post('/api/notifications', basicAuth, async (req, res) => { try { const { mobile, @@ -133,7 +135,7 @@ app.post('/api/notifications', async (req, res) => { } = req.body; const newNotification: Notification = { - id: Date.now(), + id: randomUUID(), createdAt: date, type, platforms: getPlatforms(mobile, extension), @@ -141,10 +143,10 @@ app.post('/api/notifications', async (req, res) => { title, description, content: getParsedContent(content), - extensionImageUrl: isString(extensionImageUrl) + extensionImageUrl: isNonEmptyString(extensionImageUrl) ? extensionImageUrl : getImageFallback(PlatformType.Extension, type), - mobileImageUrl: isString(mobileImageUrl) ? mobileImageUrl : getImageFallback(PlatformType.Mobile, type), + mobileImageUrl: isNonEmptyString(mobileImageUrl) ? mobileImageUrl : getImageFallback(PlatformType.Mobile, type), expirationDate, isMandatory: isDefined(isMandatory) }; diff --git a/src/middlewares/basic-auth.middleware.ts b/src/middlewares/basic-auth.middleware.ts new file mode 100644 index 0000000..04999f8 --- /dev/null +++ b/src/middlewares/basic-auth.middleware.ts @@ -0,0 +1,25 @@ +import { Request, Response, NextFunction } from 'express'; + +import { ADD_NOTIFICATION_PASSWORD, ADD_NOTIFICATION_USERNAME } from '../config'; +import { isDefined } from '../utils/helpers'; + +export const basicAuth = (req: Request, res: Response, next: NextFunction) => { + if (isDefined(req.get('Authorization'))) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const base64EncodedCredentials = req.get('Authorization')!; + const [username, password] = Buffer.from(base64EncodedCredentials.split(' ')[1], 'base64').toString().split(':'); + + if (!(username === ADD_NOTIFICATION_USERNAME && password === 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/existing-notifications.data.ts b/src/notifications/existing-notifications.data.ts index 6a274c6..6a243b5 100644 --- a/src/notifications/existing-notifications.data.ts +++ b/src/notifications/existing-notifications.data.ts @@ -4,7 +4,7 @@ import { Notification, NotificationType, PlatformType } from './notification.int //TODO: delete after deploy export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ { - id: 4, + id: '4', createdAt: '2022-12-29T16:00:00.000Z', type: NotificationType.News, platforms: [PlatformType.Mobile], @@ -24,7 +24,7 @@ export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news }, { - id: 3, + id: '3', createdAt: '2022-12-19T15:00:00.000Z', type: NotificationType.News, platforms: [PlatformType.Extension, PlatformType.Mobile], @@ -54,7 +54,7 @@ export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { - id: 2, + id: '2', createdAt: '2022-11-29T13:00:00.000Z', type: NotificationType.SecurityNote, platforms: [PlatformType.Extension], @@ -89,7 +89,7 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ isMandatory: true }, { - id: 1, + id: '1', createdAt: '2022-11-29T13:00:00.000Z', type: NotificationType.SecurityNote, platforms: [PlatformType.Mobile], @@ -123,7 +123,7 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ isMandatory: true }, { - id: 0, + id: '0', createdAt: '2022-11-29T13:00:00.000Z', type: NotificationType.News, platforms: [PlatformType.Mobile, PlatformType.Extension], diff --git a/src/notifications/notification.interface.ts b/src/notifications/notification.interface.ts index d4968aa..339f74a 100644 --- a/src/notifications/notification.interface.ts +++ b/src/notifications/notification.interface.ts @@ -15,7 +15,7 @@ export interface NotificationLink { } export interface Notification { - id: number; + id: string; createdAt: string; type: NotificationType; platforms: PlatformType[]; diff --git a/src/notifications/utils/getImageFallback.util.ts b/src/notifications/utils/get-image-fallback.util.ts similarity index 100% rename from src/notifications/utils/getImageFallback.util.ts rename to src/notifications/utils/get-image-fallback.util.ts diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts index 1394e1e..a4f8523 100644 --- a/src/notifications/utils/get-notifications.util.ts +++ b/src/notifications/utils/get-notifications.util.ts @@ -1,6 +1,6 @@ import { Redis } from 'ioredis'; -import { isString } from '../../utils/helpers'; +import { isNonEmptyString } from '../../utils/helpers'; import { Notification, PlatformType } from '../notification.interface'; import { addExistingNotificationsToDb } from './add-existing-notifications-to-db'; @@ -17,7 +17,7 @@ export const getNotifications = async (client: Redis, platform: PlatformType, st const { isMandatory, createdAt, platforms, expirationDate } = notification; const createdAtTimestamp = new Date(createdAt).getTime(); - if (isString(expirationDate) && new Date(expirationDate).getTime() < now) { + if (isNonEmptyString(expirationDate) && new Date(expirationDate).getTime() < now) { await client.lrem('notifications', 1, JSON.stringify(notification)); continue; } diff --git a/src/notifications/utils/get-parsed-content.util.ts b/src/notifications/utils/get-parsed-content.util.ts index ea18ff7..ab518a8 100644 --- a/src/notifications/utils/get-parsed-content.util.ts +++ b/src/notifications/utils/get-parsed-content.util.ts @@ -1,6 +1,8 @@ import { NotificationLink } from '../notification.interface'; -export const getParsedContent = (content: string) => +// 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[]) => { @@ -40,3 +42,26 @@ export const getParsedContent = (content: string) => 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/utils/helpers.ts b/src/utils/helpers.ts index bd5193e..2335fba 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -34,7 +34,7 @@ export const emptyFn = () => {}; export const isDefined = (value: T | undefined | null): value is T => value !== undefined && value !== null; -export const isString = (str: unknown): str is string => typeof str === 'string' && str.length !== 0; +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)); diff --git a/yarn.lock b/yarn.lock index aef39d5..1b6f954 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1006,6 +1006,13 @@ base64-js@^1.3.0, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +basic-auth@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + bignumber.js@^9.0.0, bignumber.js@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" @@ -1892,6 +1899,13 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +express-basic-auth@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381" + integrity sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA== + dependencies: + basic-auth "^2.0.1" + express-jwt@*: version "8.4.1" resolved "https://registry.yarnpkg.com/express-jwt/-/express-jwt-8.4.1.tgz#ba817c1ced7c6f1f7017fc2e6deac207011e8acb" @@ -1906,7 +1920,7 @@ express-unless@*, express-unless@^2.1.3: resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-2.1.3.tgz#f951c6cca52a24da3de32d42cfd4db57bc0f9a2e" integrity sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ== -express@^4.18.2: +express@^4.17.3, express@^4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== @@ -3210,6 +3224,15 @@ npm-run-all@^4.1.5: shell-quote "^1.6.1" string.prototype.padend "^3.0.0" +nub-auth@^0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/nub-auth/-/nub-auth-0.0.9.tgz#24ae129b34ec367d7d32f9b79ff3e76f7a866990" + integrity sha512-oB3OzrXsUB6mixtsIGQwklmslklmnDN6dQ1yiCXnRFEZGJ+uvkR+gSSbwg15w1saEg3ZrDD8XJ60NEWUuMiiEg== + dependencies: + basic-auth "^2.0.1" + express "^4.17.3" + tsscmp "^1.0.6" + object-assign@^4: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3702,6 +3725,11 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -4160,6 +4188,11 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tsscmp@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" + integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" From 7b95853c3d803df80e41e6a3917fa6dc3ed062fb Mon Sep 17 00:00:00 2001 From: lendihop Date: Wed, 5 Jul 2023 23:39:19 +0200 Subject: [PATCH 08/15] notifications migration moved to a separate script --- .gitignore | 3 --- .../notifications/data/notifications.data.ts | 5 ++--- migrations/notifications/index.ts | 13 +++++++++++++ .../utils/add-existing-notifications-to-db.ts | 3 +-- src/notifications/utils/get-notifications.util.ts | 3 --- 5 files changed, 16 insertions(+), 11 deletions(-) rename src/notifications/existing-notifications.data.ts => migrations/notifications/data/notifications.data.ts (97%) create mode 100644 migrations/notifications/index.ts rename {src => migrations}/notifications/utils/add-existing-notifications-to-db.ts (87%) diff --git a/.gitignore b/.gitignore index 150d69e..9fb2dbd 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,3 @@ dist # IDE .idea - -# Redis -dump.rdb diff --git a/src/notifications/existing-notifications.data.ts b/migrations/notifications/data/notifications.data.ts similarity index 97% rename from src/notifications/existing-notifications.data.ts rename to migrations/notifications/data/notifications.data.ts index 6a243b5..7b9d5be 100644 --- a/src/notifications/existing-notifications.data.ts +++ b/migrations/notifications/data/notifications.data.ts @@ -1,7 +1,6 @@ -import { DEFAULT_IMAGE_URLS } from './default-image-fallbacks'; -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'; -//TODO: delete after deploy export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ { id: '4', diff --git a/migrations/notifications/index.ts b/migrations/notifications/index.ts new file mode 100644 index 0000000..86a26a7 --- /dev/null +++ b/migrations/notifications/index.ts @@ -0,0 +1,13 @@ +import { Redis } from 'ioredis'; +import { REDIS_URL } from '../../src/config'; +import { addExistingNotificationsToDb } from './utils/add-existing-notifications-to-db'; +import logger from '../../src/utils/logger'; + +const redisClient = new Redis(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/src/notifications/utils/add-existing-notifications-to-db.ts b/migrations/notifications/utils/add-existing-notifications-to-db.ts similarity index 87% rename from src/notifications/utils/add-existing-notifications-to-db.ts rename to migrations/notifications/utils/add-existing-notifications-to-db.ts index adf2722..d011751 100644 --- a/src/notifications/utils/add-existing-notifications-to-db.ts +++ b/migrations/notifications/utils/add-existing-notifications-to-db.ts @@ -1,8 +1,7 @@ import { Redis } from 'ioredis'; -import { DEFAULT_NOTIFICATIONS_LIST, MANDATORY_NOTIFICATIONS_LIST } from '../existing-notifications.data'; +import { DEFAULT_NOTIFICATIONS_LIST, MANDATORY_NOTIFICATIONS_LIST } from '../data/notifications.data'; -//TODO: delete after deploy export const addExistingNotificationsToDb = async (client: Redis) => { const data = await client.lrange('notifications', 0, -1); const existingNotifications = [...DEFAULT_NOTIFICATIONS_LIST, ...MANDATORY_NOTIFICATIONS_LIST]; diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts index a4f8523..451b0d6 100644 --- a/src/notifications/utils/get-notifications.util.ts +++ b/src/notifications/utils/get-notifications.util.ts @@ -2,11 +2,8 @@ import { Redis } from 'ioredis'; import { isNonEmptyString } from '../../utils/helpers'; import { Notification, PlatformType } from '../notification.interface'; -import { addExistingNotificationsToDb } from './add-existing-notifications-to-db'; export const getNotifications = async (client: Redis, platform: PlatformType, startFromTime: number) => { - await addExistingNotificationsToDb(client); - const data = await client.lrange('notifications', 0, -1); const now = Date.now(); From 606cbb1c12bc13487ffb9e9b8917b3efdd92bb97 Mon Sep 17 00:00:00 2001 From: lendihop Date: Thu, 6 Jul 2023 10:34:52 +0200 Subject: [PATCH 09/15] added migration script to package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c3dc95..8a8f9fc 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "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", From b2b39f1ffb8a6299143dc48eed793f29046b087e Mon Sep 17 00:00:00 2001 From: lendihop Date: Fri, 7 Jul 2023 15:37:19 +0200 Subject: [PATCH 10/15] sort notifications on every get request --- src/index.ts | 2 -- src/middlewares/basic-auth.middleware.ts | 6 +++--- .../utils/get-notifications.util.ts | 12 +++++++----- src/notifications/utils/sort-notifications.ts | 17 ----------------- 4 files changed, 10 insertions(+), 27 deletions(-) delete mode 100644 src/notifications/utils/sort-notifications.ts diff --git a/src/index.ts b/src/index.ts index 077d02a..159eda5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,6 @@ 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 { sortNotifications } from './notifications/utils/sort-notifications'; 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'; @@ -152,7 +151,6 @@ app.post('/api/notifications', basicAuth, async (req, res) => { }; await redisClient.lpush('notifications', JSON.stringify(newNotification)); - await sortNotifications(redisClient); res.status(200).send({ message: 'Notification added successfully' }); } catch (error: any) { diff --git a/src/middlewares/basic-auth.middleware.ts b/src/middlewares/basic-auth.middleware.ts index 04999f8..a2d8b29 100644 --- a/src/middlewares/basic-auth.middleware.ts +++ b/src/middlewares/basic-auth.middleware.ts @@ -4,9 +4,9 @@ import { ADD_NOTIFICATION_PASSWORD, ADD_NOTIFICATION_USERNAME } from '../config' import { isDefined } from '../utils/helpers'; export const basicAuth = (req: Request, res: Response, next: NextFunction) => { - if (isDefined(req.get('Authorization'))) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const base64EncodedCredentials = req.get('Authorization')!; + const base64EncodedCredentials = req.get('Authorization'); + + if (isDefined(base64EncodedCredentials)) { const [username, password] = Buffer.from(base64EncodedCredentials.split(' ')[1], 'base64').toString().split(':'); if (!(username === ADD_NOTIFICATION_USERNAME && password === ADD_NOTIFICATION_PASSWORD)) { diff --git a/src/notifications/utils/get-notifications.util.ts b/src/notifications/utils/get-notifications.util.ts index 451b0d6..d6ae3a3 100644 --- a/src/notifications/utils/get-notifications.util.ts +++ b/src/notifications/utils/get-notifications.util.ts @@ -5,17 +5,19 @@ 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 < data.length; i++) { - const notification: Notification = JSON.parse(data[i]); - const { isMandatory, createdAt, platforms, expirationDate } = 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(notification)); + await client.lrem('notifications', 1, JSON.stringify(notifications[i])); continue; } @@ -23,7 +25,7 @@ export const getNotifications = async (client: Redis, platform: PlatformType, st platforms.includes(platform) && (isMandatory === true || (createdAtTimestamp > startFromTime && createdAtTimestamp < now)) ) { - result.push(notification); + result.push(notifications[i]); } } diff --git a/src/notifications/utils/sort-notifications.ts b/src/notifications/utils/sort-notifications.ts deleted file mode 100644 index d91bb3f..0000000 --- a/src/notifications/utils/sort-notifications.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Redis } from 'ioredis'; - -import { Notification } from '../notification.interface'; - -export const sortNotifications = async (client: Redis) => { - const data = await client.lrange('notifications', 0, -1); - const notifications: Notification[] = data.map(item => JSON.parse(item)); - const sortedNotifications = notifications.sort( - (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ); - - await client.del('notifications'); - - for (let i = 0; i < sortedNotifications.length; i++) { - await client.rpush('notifications', JSON.stringify(sortedNotifications[i])); - } -}; From 7b12643029f2f3bc61027463c7a07e41b8899101 Mon Sep 17 00:00:00 2001 From: lendihop Date: Fri, 7 Jul 2023 16:10:39 +0200 Subject: [PATCH 11/15] remove indent --- src/notifications/utils/get-parsed-content.util.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/notifications/utils/get-parsed-content.util.ts b/src/notifications/utils/get-parsed-content.util.ts index ab518a8..5bb2f45 100644 --- a/src/notifications/utils/get-parsed-content.util.ts +++ b/src/notifications/utils/get-parsed-content.util.ts @@ -1,7 +1,6 @@ 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') From 908f3e1fcbef85277a507cae71c92d3771230b21 Mon Sep 17 00:00:00 2001 From: lendihop Date: Fri, 7 Jul 2023 16:27:24 +0200 Subject: [PATCH 12/15] refactor according to review comments --- src/config.ts | 36 +++++++------------ src/index.ts | 7 ++-- src/middlewares/basic-auth.middleware.ts | 4 +-- src/redis.ts | 7 ++++ .../get-alice-bob-request-headers.ts | 4 +-- .../alice-bob/get-alice-bob-signature.ts | 4 +-- src/utils/moonpay/get-signed-moonpay-url.ts | 4 +-- src/utils/three-route.ts | 6 ++-- 8 files changed, 33 insertions(+), 39 deletions(-) create mode 100644 src/redis.ts diff --git a/src/config.ts b/src/config.ts index e1db954..4cfd913 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,27 +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 REDIS_URL = getEnv('REDIS_URL'); -export const ADD_NOTIFICATION_USERNAME = getEnv('ADD_NOTIFICATION_USERNAME'); -export const ADD_NOTIFICATION_PASSWORD = getEnv('ADD_NOTIFICATION_PASSWORD'); +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 }, - { name: 'REDIS_URL', value: REDIS_URL }, - { name: 'ADD_NOTIFICATION_USERNAME', value: ADD_NOTIFICATION_USERNAME }, - { name: 'ADD_NOTIFICATION_PASSWORD', value: ADD_NOTIFICATION_PASSWORD } -]; -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 159eda5..15e200c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,12 +5,11 @@ import cors from 'cors'; import { randomUUID } from 'crypto'; import express, { Request, Response } from 'express'; import firebaseAdmin from 'firebase-admin'; -import { Redis } from 'ioredis'; import { stdSerializers } from 'pino'; import pinoHttp from 'pino-http'; import { getAdvertisingInfo } from './advertising/advertising'; -import { MIN_ANDROID_APP_VERSION, MIN_IOS_APP_VERSION, REDIS_URL } from './config'; +import { MIN_ANDROID_APP_VERSION, MIN_IOS_APP_VERSION } from './config'; import getDAppsStats from './getDAppsStats'; import { basicAuth } from './middlewares/basic-auth.middleware'; import { Notification, PlatformType } from './notifications/notification.interface'; @@ -18,6 +17,7 @@ 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'; @@ -59,9 +59,6 @@ app.use(pinoHttp(PINO_LOGGER)); app.use(cors()); app.use(bodyParser.json()); -const redisClient = new Redis(REDIS_URL); -redisClient.on('error', err => logger.error(err)); - const dAppsProvider = new SingleQueryDataProvider(15 * 60 * 1000, getDAppsStats); const androidApp = firebaseAdmin.initializeApp( diff --git a/src/middlewares/basic-auth.middleware.ts b/src/middlewares/basic-auth.middleware.ts index a2d8b29..a9d7ad5 100644 --- a/src/middlewares/basic-auth.middleware.ts +++ b/src/middlewares/basic-auth.middleware.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express'; -import { ADD_NOTIFICATION_PASSWORD, ADD_NOTIFICATION_USERNAME } from '../config'; +import { EnvVars } from '../config'; import { isDefined } from '../utils/helpers'; export const basicAuth = (req: Request, res: Response, next: NextFunction) => { @@ -9,7 +9,7 @@ export const basicAuth = (req: Request, res: Response, next: NextFunction) => { if (isDefined(base64EncodedCredentials)) { const [username, password] = Buffer.from(base64EncodedCredentials.split(' ')[1], 'base64').toString().split(':'); - if (!(username === ADD_NOTIFICATION_USERNAME && password === ADD_NOTIFICATION_PASSWORD)) { + if (!(username === EnvVars.ADD_NOTIFICATION_USERNAME && password === EnvVars.ADD_NOTIFICATION_PASSWORD)) { handleNotAuthenticated(res, next); } next(); 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/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 f60d9ce..8dc396b 100644 --- a/src/utils/alice-bob/get-alice-bob-signature.ts +++ b/src/utils/alice-bob/get-alice-bob-signature.ts @@ -1,6 +1,6 @@ import crypto from 'crypto'; -import { ALICE_BOB_PRIVATE_KEY } from '../../config'; +import { EnvVars } from '../../config'; import { AliceBobPayload } from '../../interfaces/alice-bob.interfaces'; export const getAliceBobSignature = (payload?: AliceBobPayload) => { @@ -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/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/three-route.ts b/src/utils/three-route.ts index 19c2615..b199f22 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 { @@ -93,9 +93,9 @@ type ThreeRouteQueryParams = object | SwapQueryParams; type ThreeRouteQueryResponse = ThreeRouteSwapResponse | ThreeRouteDex[] | ThreeRouteToken[]; 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( From 7562537b0184507fc5f4148e0de070319480047e Mon Sep 17 00:00:00 2001 From: lendihop Date: Mon, 10 Jul 2023 13:27:06 +0200 Subject: [PATCH 13/15] +Date.now() --- migrations/notifications/data/notifications.data.ts | 10 +++++----- src/index.ts | 3 +-- src/notifications/notification.interface.ts | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/migrations/notifications/data/notifications.data.ts b/migrations/notifications/data/notifications.data.ts index 7b9d5be..b845f7c 100644 --- a/migrations/notifications/data/notifications.data.ts +++ b/migrations/notifications/data/notifications.data.ts @@ -3,7 +3,7 @@ import { Notification, NotificationType, PlatformType } from '../../../src/notif export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ { - id: '4', + id: 4, createdAt: '2022-12-29T16:00:00.000Z', type: NotificationType.News, platforms: [PlatformType.Mobile], @@ -23,7 +23,7 @@ export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ mobileImageUrl: DEFAULT_IMAGE_URLS.mobile.news }, { - id: '3', + id: 3, createdAt: '2022-12-19T15:00:00.000Z', type: NotificationType.News, platforms: [PlatformType.Extension, PlatformType.Mobile], @@ -53,7 +53,7 @@ export const DEFAULT_NOTIFICATIONS_LIST: Notification[] = [ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ { - id: '2', + id: 2, createdAt: '2022-11-29T13:00:00.000Z', type: NotificationType.SecurityNote, platforms: [PlatformType.Extension], @@ -88,7 +88,7 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ isMandatory: true }, { - id: '1', + id: 1, createdAt: '2022-11-29T13:00:00.000Z', type: NotificationType.SecurityNote, platforms: [PlatformType.Mobile], @@ -122,7 +122,7 @@ export const MANDATORY_NOTIFICATIONS_LIST: Notification[] = [ isMandatory: true }, { - id: '0', + id: 0, createdAt: '2022-11-29T13:00:00.000Z', type: NotificationType.News, platforms: [PlatformType.Mobile, PlatformType.Extension], diff --git a/src/index.ts b/src/index.ts index 15e200c..ac0c5cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,6 @@ require('./configure'); import bodyParser from 'body-parser'; import cors from 'cors'; -import { randomUUID } from 'crypto'; import express, { Request, Response } from 'express'; import firebaseAdmin from 'firebase-admin'; import { stdSerializers } from 'pino'; @@ -131,7 +130,7 @@ app.post('/api/notifications', basicAuth, async (req, res) => { } = req.body; const newNotification: Notification = { - id: randomUUID(), + id: Date.now(), createdAt: date, type, platforms: getPlatforms(mobile, extension), diff --git a/src/notifications/notification.interface.ts b/src/notifications/notification.interface.ts index 339f74a..d4968aa 100644 --- a/src/notifications/notification.interface.ts +++ b/src/notifications/notification.interface.ts @@ -15,7 +15,7 @@ export interface NotificationLink { } export interface Notification { - id: string; + id: number; createdAt: string; type: NotificationType; platforms: PlatformType[]; From 82e19f02d486583bdb80cc5ae60801936f7b61b8 Mon Sep 17 00:00:00 2001 From: Korney Vasilchenko Date: Tue, 11 Jul 2023 17:38:20 +0100 Subject: [PATCH 14/15] Fix broken notifications migration --- migrations/notifications/index.ts | 7 +++++-- yarn.lock | 35 +------------------------------ 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/migrations/notifications/index.ts b/migrations/notifications/index.ts index 86a26a7..ec50573 100644 --- a/migrations/notifications/index.ts +++ b/migrations/notifications/index.ts @@ -1,9 +1,12 @@ +import '../../src/configure'; + import { Redis } from 'ioredis'; -import { REDIS_URL } from '../../src/config'; +import { EnvVars } from '../../src/config'; import { addExistingNotificationsToDb } from './utils/add-existing-notifications-to-db'; import logger from '../../src/utils/logger'; -const redisClient = new Redis(REDIS_URL); +const redisClient = new Redis(EnvVars.REDIS_URL); + redisClient.on('error', err => logger.error(err)); (async () => { diff --git a/yarn.lock b/yarn.lock index 1b6f954..aef39d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1006,13 +1006,6 @@ base64-js@^1.3.0, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -basic-auth@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" - integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== - dependencies: - safe-buffer "5.1.2" - bignumber.js@^9.0.0, bignumber.js@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" @@ -1899,13 +1892,6 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -express-basic-auth@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381" - integrity sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA== - dependencies: - basic-auth "^2.0.1" - express-jwt@*: version "8.4.1" resolved "https://registry.yarnpkg.com/express-jwt/-/express-jwt-8.4.1.tgz#ba817c1ced7c6f1f7017fc2e6deac207011e8acb" @@ -1920,7 +1906,7 @@ express-unless@*, express-unless@^2.1.3: resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-2.1.3.tgz#f951c6cca52a24da3de32d42cfd4db57bc0f9a2e" integrity sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ== -express@^4.17.3, express@^4.18.2: +express@^4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== @@ -3224,15 +3210,6 @@ npm-run-all@^4.1.5: shell-quote "^1.6.1" string.prototype.padend "^3.0.0" -nub-auth@^0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/nub-auth/-/nub-auth-0.0.9.tgz#24ae129b34ec367d7d32f9b79ff3e76f7a866990" - integrity sha512-oB3OzrXsUB6mixtsIGQwklmslklmnDN6dQ1yiCXnRFEZGJ+uvkR+gSSbwg15w1saEg3ZrDD8XJ60NEWUuMiiEg== - dependencies: - basic-auth "^2.0.1" - express "^4.17.3" - tsscmp "^1.0.6" - object-assign@^4: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3725,11 +3702,6 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" -safe-buffer@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -4188,11 +4160,6 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tsscmp@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" - integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" From 330c509b50464f6c2a2ee4d3d2e24e1f085540a0 Mon Sep 17 00:00:00 2001 From: lendihop Date: Thu, 17 Aug 2023 18:47:56 +0200 Subject: [PATCH 15/15] delete picky.fi --- src/utils/dapp-list-constants.ts | 8 -------- 1 file changed, 8 deletions(-) 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] } ];