From 12f0d853f12593df1c839e83cbdf55b49044d6d1 Mon Sep 17 00:00:00 2001 From: Jake Naviasky Date: Thu, 16 Mar 2023 17:40:02 -0400 Subject: [PATCH] Revert "Remove Chain Event Types #3 (#3044)" (#3163) * Revert "Remove Chain Event Types #3 (#3044)" This reverts commit 68ba0c5c4d3e6a3598862b37e377bcfd29f85bb0. * revert migration * add back the migrations that did run successfully * txn * fix --------- Co-authored-by: Timothee Legros --- .prettierignore | 2 - packages/chain-events/Procfile | 10 +- packages/chain-events/package.json | 7 +- .../scripts/migrateChainEntities.ts | 14 +- .../scripts/publishCustomRabbitMQMessage.ts | 83 ----- .../scripts/testSequelizeQuery.ts | 21 ++ .../ChainEventHandlers/entityArchival.ts | 8 +- .../ChainEventHandlers/migration.ts | 71 ++++- .../ChainEventHandlers/notification.ts | 37 +-- .../ChainEventHandlers/storage.ts | 51 ++- .../chainEventsConsumer.ts | 2 +- .../ChainEventsConsumer/republishMessages.ts | 52 ++++ .../services/ChainSubscriber/util.ts | 34 +- .../services/app/routes/entities.ts | 1 + .../services/app/routes/eventActivity.ts | 23 +- .../services/database/database.ts | 4 + .../20221206094622-move-chain-to-event.js | 40 --- .../20221206100637-move-network-to-event.js | 31 -- ...20221221220946-remove-chain-event-types.js | 45 --- .../services/database/models/chain_entity.ts | 6 +- .../services/database/models/chain_event.ts | 22 +- .../database/models/chain_event_type.ts | 49 +++ packages/chain-events/src/interfaces.ts | 22 -- .../service-tests/chainEventsConsumer.test.ts | 20 ++ .../src/rabbitmq/rabbitMQConfig.ts | 19 ++ .../src/rabbitmq/rabbitMQController.ts | 4 +- .../types/chainEventNotificationsCUD.ts | 13 +- .../src/rabbitmq/types/chainEventTypeCUD.ts | 41 +++ .../common-common/src/rabbitmq/types/index.ts | 8 + .../controllers/server/chain_entities.ts | 26 +- .../controllers/server/notifications.ts | 33 +- .../server/socket/chainEventsNs.ts | 23 +- .../client/scripts/helpers/getFetch.ts | 23 -- .../client/scripts/helpers/getUrl.ts | 48 +++ .../client/scripts/models/ChainEvent.ts | 18 +- .../client/scripts/models/ChainEventType.ts | 2 + .../models/DashboardActivityNotification.ts | 8 +- .../client/scripts/models/Notification.ts | 11 +- .../models/NotificationSubscription.ts | 12 +- .../client/scripts/models/index.ts | 1 + .../views/components/notification_row.ts | 4 +- .../pages/notification_settings/index.tsx | 152 +-------- .../views/pages/user_dashboard/helpers.ts | 10 +- .../user_dashboard_chain_event_row.tsx | 2 +- .../pages/notification_settings/index.scss | 12 - packages/commonwealth/server-test.ts | 2 +- packages/commonwealth/server.ts | 4 +- .../CommonwealthConsumer.ts | 8 + .../chainEventNotificationsCUDQueue.ts | 11 +- .../chainEventTypeCUDQueue.ts | 27 ++ packages/commonwealth/server/config.ts | 3 +- packages/commonwealth/server/database.ts | 2 + .../20221226212006-remove-unused-entity-id.js | 2 +- ...26221226-delete-existing-ce-subs-notifs.js | 294 ------------------ .../20230316212013-revert-cet-migrations.js | 34 ++ packages/commonwealth/server/models.ts | 2 + .../server/models/chain_event_type.ts | 38 +++ .../server/models/notification.ts | 5 +- .../server/models/subscription.ts | 12 + .../commonwealth/server/routes/deleteChain.ts | 6 + .../server/routes/getChainEventServiceData.ts | 1 + .../server/routes/getSubscribedChains.ts | 12 +- .../routes/subscription/createSubscription.ts | 44 ++- .../routes/subscription/viewSubscriptions.ts | 4 + .../server/routes/viewNotifications.ts | 5 + .../commonwealth/server/scripts/emails.ts | 48 ++- .../server/scripts/enforceDataConsistency.ts | 22 ++ .../server/socket/createNamespace.ts | 23 +- .../server/util/emitNotifications.ts | 56 ++-- .../commonwealth/server/util/entitiesProxy.ts | 37 +-- packages/commonwealth/shared/types.ts | 6 +- .../enforceDataConsistency.spec.ts | 52 +++- .../events/entityArchivalHandler.spec.ts | 33 ++ .../events/migrationHandler.spec.ts | 24 +- .../events/rabbitmqProducer.spec.ts | 2 +- .../integration/events/storageHandler.spec.ts | 28 +- .../test/integration/events/util/index.ts | 63 ++++ .../test/systemTests/consumer.test.ts | 37 ++- scripts/load-env-var.sh | 2 +- 79 files changed, 1087 insertions(+), 987 deletions(-) delete mode 100644 packages/chain-events/scripts/publishCustomRabbitMQMessage.ts create mode 100644 packages/chain-events/scripts/testSequelizeQuery.ts create mode 100644 packages/chain-events/services/ChainEventsConsumer/republishMessages.ts delete mode 100644 packages/chain-events/services/database/migrations/20221206094622-move-chain-to-event.js delete mode 100644 packages/chain-events/services/database/migrations/20221206100637-move-network-to-event.js delete mode 100644 packages/chain-events/services/database/migrations/20221221220946-remove-chain-event-types.js create mode 100644 packages/chain-events/services/database/models/chain_event_type.ts create mode 100644 packages/common-common/src/rabbitmq/types/chainEventTypeCUD.ts delete mode 100644 packages/commonwealth/client/scripts/helpers/getFetch.ts create mode 100644 packages/commonwealth/client/scripts/helpers/getUrl.ts create mode 100644 packages/commonwealth/server/CommonwealthConsumer/messageProcessors/chainEventTypeCUDQueue.ts delete mode 100644 packages/commonwealth/server/migrations/20221226221226-delete-existing-ce-subs-notifs.js create mode 100644 packages/commonwealth/server/migrations/20230316212013-revert-cet-migrations.js create mode 100644 packages/commonwealth/server/models/chain_event_type.ts create mode 100644 packages/commonwealth/test/integration/events/util/index.ts diff --git a/.prettierignore b/.prettierignore index 961dd96231b..f1afd7481b5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,4 @@ **/build -**/dist **/.github **/*.env **/node_modules @@ -17,7 +16,6 @@ packages/chain-events/.eslintrc.js **/contractTypes **/factories **/eth/types -**/eth/artifacts **/.eslintrc.js **/ios/App/App diff --git a/packages/chain-events/Procfile b/packages/chain-events/Procfile index 79a89848f55..b64bf8a8628 100644 --- a/packages/chain-events/Procfile +++ b/packages/chain-events/Procfile @@ -1,6 +1,6 @@ -web: node --max_old_space_size=$(./scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/app/Server.js -subscriber1: CHAIN_SUBSCRIBER_INDEX=0 node --max_old_space_size=$(./scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainSubscriber/chainSubscriber.js run-as-script -subscriber2: CHAIN_SUBSCRIBER_INDEX=1 node --max_old_space_size=$(./scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainSubscriber/chainSubscriber.js run-as-script -subscriber3: CHAIN_SUBSCRIBER_INDEX=2 node --max_old_space_size=$(./scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainSubscriber/chainSubscriber.js run-as-script -consumer: node --max_old_space_size=$(./scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainEventsConsumer/chainEventsConsumer.js run-as-script +web: node --max_old_space_size=$(../../../../scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/app/Server.js +subscriber1: CHAIN_SUBSCRIBER_INDEX=0 node --max_old_space_size=$(../../../../scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainSubscriber/chainSubscriber.js run-as-script +subscriber2: CHAIN_SUBSCRIBER_INDEX=1 node --max_old_space_size=$(../../../../scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainSubscriber/chainSubscriber.js run-as-script +subscriber3: CHAIN_SUBSCRIBER_INDEX=2 node --max_old_space_size=$(../../../../scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainSubscriber/chainSubscriber.js run-as-script +consumer: node --max_old_space_size=$(../../../../scripts/get-max-old-space-size.sh) packages/chain-events/build/chain-events/services/ChainEventsConsumer/chainEventsConsumer.js run-as-script release: cd packages/chain-events && npx sequelize-cli db:migrate --config services/database/sequelize.json diff --git a/packages/chain-events/package.json b/packages/chain-events/package.json index 9e7bd9f0c33..9a0970a6eb1 100644 --- a/packages/chain-events/package.json +++ b/packages/chain-events/package.json @@ -29,9 +29,9 @@ "batch-poll": "ts-node -T ./scripts/batchPoller.ts", "preyalcpublish": "yarn build", "emit-events": "ts-node --project tsconfig.json ./scripts/emitChainEvents.ts", - "start-app": "ts-node-dev --max-old-space-size=4096 --respawn --transpile-only --project tsconfig.services.json services/app/Server.ts", - "start-consumer": "ts-node-dev --max-old-space-size=4096 --respawn --transpile-only --project tsconfig.services.json services/ChainEventsConsumer/chainEventsConsumer.ts run-as-script", - "start-subscriber": "ts-node-dev --max-old-space-size=4096 --respawn --transpile-only --project tsconfig.services.json services/ChainSubscriber/chainSubscriber.ts run-as-script", + "start-app": "ts-node --project tsconfig.services.json services/app/Server.ts", + "start-consumer": "ts-node --project tsconfig.services.json services/ChainEventsConsumer/chainEventsConsumer.ts run-as-script", + "start-subscriber": "ts-node --project tsconfig.services.json services/ChainSubscriber/chainSubscriber.ts run-as-script", "start-all": "concurrently -p '{name}' -c yellow,blue,magenta -n app,consumer,subscriber 'yarn start-app' 'yarn start-consumer' 'yarn start-subscriber'", "create-db": "npx sequelize db:create", "migrate-db": "npx sequelize db:migrate", @@ -80,7 +80,6 @@ "pg-format": "^1.0.4", "rollbar": "^2.25.2", "sleep-promise": "^8.0.1", - "ts-node-dev": "^2.0.0", "typescript-logging": "^0.6.4", "underscore": "^1.10.2", "web3": "^1.3.1", diff --git a/packages/chain-events/scripts/migrateChainEntities.ts b/packages/chain-events/scripts/migrateChainEntities.ts index 78477a4891b..fb7623b5a1e 100644 --- a/packages/chain-events/scripts/migrateChainEntities.ts +++ b/packages/chain-events/scripts/migrateChainEntities.ts @@ -28,9 +28,6 @@ import type { BrokerConfig } from 'rascal'; import { RABBITMQ_URI } from '../../commonwealth/server/config'; import { constructSubstrateUrl } from '../../commonwealth/shared/substrate'; import { CHAIN_EVENT_SERVICE_SECRET, CW_SERVER_URL } from '../services/config'; -import NotificationsHandler from '../services/ChainEventsConsumer/ChainEventHandlers/notification'; -import models from '../services/database/database'; -import { EventKind } from '../src/chains/substrate/types'; const log = factory.getLogger(formatFilename(__filename)); @@ -98,14 +95,6 @@ async function migrateChainEntity( rmqController, chain ); - - const excludedNotificationEvents = [EventKind.DemocracyTabled]; - const notificationsHandler = new NotificationsHandler( - models, - rmqController, - excludedNotificationEvents - ); - let fetcher: IStorageFetcher; const range: IDisconnectedRange = { startBlock: 0 }; if (chainInstance.base === ChainBase.Substrate) { @@ -164,8 +153,7 @@ async function migrateChainEntity( try { // eslint-disable-next-line no-await-in-loop const dbEvent = await migrationHandler.handle(event); - const ceEvent = await entityArchivalHandler.handle(event, dbEvent); - await notificationsHandler.handle(event, ceEvent); + await entityArchivalHandler.handle(event, dbEvent); } catch (e) { log.error(`Event handle failure: ${e.message}`); } diff --git a/packages/chain-events/scripts/publishCustomRabbitMQMessage.ts b/packages/chain-events/scripts/publishCustomRabbitMQMessage.ts deleted file mode 100644 index 9a9bd46334b..00000000000 --- a/packages/chain-events/scripts/publishCustomRabbitMQMessage.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { IEventData } from '../src/chains/aave/types'; -import type { CWEvent } from '../src'; -import { SupportedNetwork } from '../src'; -import { publishRmqMsg } from 'common-common/src/rabbitmq/util'; -import { RABBITMQ_API_URI } from '../services/config'; -import { RascalExchanges, RascalRoutingKeys } from 'common-common/src/rabbitmq'; -import models from 'chain-events/services/database/database'; - -async function main() { - const ceData = { - id: 10, - kind: 'proposal-created', - values: ['0'], - targets: ['0xE710CEd57456D3A16152c32835B5FB4E72D9eA5b'], - endBlock: 16203604, - executor: '0x64c7d40c07EFAbec2AafdC243bF59eaF2195c6dc', - ipfsHash: - '0x3876d28a014bc20432dcc3549ba95710446b98431d84c7f84fde6abe1baf527f', - proposer: '0xb55a948763e0d386b6dEfcD8070a522216AE42b1', - strategy: '0x90Dfd35F4a0BB2d30CDf66508085e33C353475D9', - calldatas: [ - '0x00000000000000000000000092d6c1e31e14520e676a687f0a93788b716beff5000000000000000000000000a8541f948411b3f95d9e89e8d339a56a9ed3d00b000000000000000000000000000000000000000000002fa54641bae8aaa00000', - ], - signatures: ['transfer(address,address,uint256)'], - startBlock: 16177324, - }; - const chainEvent: CWEvent = { - blockNumber: 16170754, - data: ceData, - network: SupportedNetwork.Aave, - chain: 'dydx', - }; - - const publishJson = await publishRmqMsg( - RABBITMQ_API_URI, - RascalExchanges.ChainEvents, - RascalRoutingKeys.ChainEvents, - chainEvent - ); - - console.log(publishJson); -} - -async function clear() { - try { - let eventsDeleted = 0, - entitiesDeleted = 0; - await models.sequelize.transaction(async (t) => { - const entityId = ( - await models.ChainEntity.findOne({ - where: { chain: 'dydx', type_id: '10' }, - transaction: t, - }) - )?.id; - - if (entityId) { - eventsDeleted = await models.ChainEvent.destroy({ - where: { entity_id: entityId }, - transaction: t, - }); - - entitiesDeleted = await models.ChainEntity.destroy({ - where: { id: entityId }, - transaction: t, - }); - } else { - console.log('Entity does not exist.'); - } - }); - - console.log( - `Events deleted: ${eventsDeleted}\nEntities deleted: ${entitiesDeleted}` - ); - } catch (e) { - console.log('Failed to clear - reverted.'); - console.error(e); - } - - process.exit(1); -} - -if (process.argv[2] === 'clear') clear(); -else main(); diff --git a/packages/chain-events/scripts/testSequelizeQuery.ts b/packages/chain-events/scripts/testSequelizeQuery.ts new file mode 100644 index 00000000000..46015304ac4 --- /dev/null +++ b/packages/chain-events/scripts/testSequelizeQuery.ts @@ -0,0 +1,21 @@ +import models from '../services/database/database'; + +async function testSequelizeQuery() { + const eventTypes = ( + await models.ChainEventType.findAll({ + attributes: ['id'], + where: { chain: 'edgeware' }, + }) + ).map((x) => x.id); + + const dbResult = await models.ChainEvent.max('block_number', { + where: { + chain_event_type_id: eventTypes, + }, + }); + + console.log('Database Result:', dbResult); + process.exit(0); +} + +testSequelizeQuery(); diff --git a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/entityArchival.ts b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/entityArchival.ts index 8a0253a9038..39989846aa9 100644 --- a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/entityArchival.ts +++ b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/entityArchival.ts @@ -9,7 +9,6 @@ import { RascalPublications } from 'common-common/src/rabbitmq/types'; import type { DB } from '../../database/database'; -import type { ChainEventInstance } from 'chain-events/services/database/models/chain_event'; import type { CWEvent, IChainEntityKind, @@ -41,10 +40,7 @@ export default class extends IEventHandler { * `dbEvent` is the database entry corresponding to the `event`. */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - public async handle( - event: CWEvent, - dbEvent: ChainEventInstance - ) { + public async handle(event: CWEvent, dbEvent) { // eslint-disable-next-line @typescript-eslint/no-shadow const log = factory.getLogger( addPrefix(__filename, [event.network, event.chain]) @@ -177,7 +173,5 @@ export default class extends IEventHandler { break; } } - - return dbEvent; } } diff --git a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/migration.ts b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/migration.ts index bb70619778b..ce476149235 100644 --- a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/migration.ts +++ b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/migration.ts @@ -2,22 +2,26 @@ * Processes events during migration, upgrading from simple notifications to entities. */ import type { WhereOptions } from 'sequelize'; -import type { RabbitMQController } from 'common-common/src/rabbitmq'; +import type { + RabbitMQController, + RmqCENotificationCUD, + RmqCETypeCUD, +} from 'common-common/src/rabbitmq'; +import { RascalPublications } from 'common-common/src/rabbitmq'; import { factory, formatFilename } from 'common-common/src/logging'; import type { CWEvent } from '../../../src'; import { - EntityEventKind, + IEventHandler, eventToEntity, getUniqueEntityKey, - IEventHandler, + EntityEventKind, } from '../../../src'; import type { DB } from '../../database/database'; import type { ChainEventAttributes, ChainEventInstance, } from '../../database/models/chain_event'; - const log = factory.getLogger(formatFilename(__filename)); export default class extends IEventHandler { @@ -43,18 +47,48 @@ export default class extends IEventHandler { fieldValue: string, eventType: EntityEventKind ) => { + const [dbEventType, created] = + await this._models.ChainEventType.findOrCreate({ + where: { + id: `${chain}-${event.data.kind.toString()}`, + chain, + event_network: event.network, + event_name: event.data.kind.toString(), + }, + }); + log.trace( + `${created ? 'created' : 'found'} chain event type: ${dbEventType.id}` + ); + + if (created) { + const publishData: RmqCETypeCUD.RmqMsgType = { + chainEventTypeId: dbEventType.id, + cud: 'create', + }; + + await this._rmqController.safePublish( + publishData, + dbEventType.id, + RascalPublications.ChainEventTypeCUDMain, + { + sequelize: this._models.sequelize, + model: this._models.ChainEventType, + } + ); + } + const queryFieldName = `event_data.${fieldName}`; const queryArgs: WhereOptions = eventType === EntityEventKind.Vote ? { + chain_event_type_id: dbEventType.id, [queryFieldName]: fieldValue, // votes will be unique by data rather than by type event_data: event.data as any, - chain, } : { + chain_event_type_id: dbEventType.id, [queryFieldName]: fieldValue, - chain, }; const existingEvent = await this._models.ChainEvent.findOne({ where: queryArgs, @@ -67,12 +101,31 @@ export default class extends IEventHandler { } log.info('No existing event found, creating new event in db!'); - return await this._models.ChainEvent.create({ + const dbEvent = await this._models.ChainEvent.create({ + chain_event_type_id: dbEventType.id, block_number: event.blockNumber, event_data: event.data, - network: event.network, - chain, }); + + const formattedEvent: ChainEventAttributes = dbEvent.toJSON(); + formattedEvent.ChainEventType = dbEventType.toJSON(); + + const publishData: RmqCENotificationCUD.RmqMsgType = { + ChainEvent: formattedEvent, + event, + cud: 'create', + }; + + await this._rmqController.safePublish( + publishData, + dbEvent.id, + RascalPublications.ChainEventNotificationsCUDMain, + { + sequelize: this._models.sequelize, + model: this._models.ChainEvent, + } + ); + return dbEvent; }; const entity = eventToEntity(event.network, event.data.kind); diff --git a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/notification.ts b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/notification.ts index b62a059c6c1..9d243bacc9a 100644 --- a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/notification.ts +++ b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/notification.ts @@ -3,15 +3,11 @@ import type { RmqCENotificationCUD } from 'common-common/src/rabbitmq/types'; import { RascalPublications } from 'common-common/src/rabbitmq/types'; import { addPrefix, factory } from '../../../src/logging'; -import type { ChainEventInstance } from '../../database/models/chain_event'; +import type { ChainEventAttributes } from '../../database/models/chain_event'; import type { DB } from '../../database/database'; import type { CWEvent, IChainEventKind } from 'chain-events/src'; -import { - EntityEventKind, - eventToEntity, - IEventHandler, -} from 'chain-events/src'; +import { IEventHandler } from 'chain-events/src'; export default class extends IEventHandler { public readonly name = 'Notification Producer'; @@ -25,7 +21,7 @@ export default class extends IEventHandler { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - public async handle(event: CWEvent, dbEvent: ChainEventInstance) { + public async handle(event: CWEvent, dbEvent) { const log = factory.getLogger( addPrefix(__filename, [event.network, event.chain]) ); @@ -40,24 +36,25 @@ export default class extends IEventHandler { return dbEvent; } - if (!dbEvent.entity_id) { - log.info(`No related entity, skipping!`); - return dbEvent; - } - - const [, eventEntityKind] = eventToEntity(event.network, event.data.kind); - if ( - eventEntityKind != EntityEventKind.Create && - eventEntityKind != EntityEventKind.Complete - ) { - log.trace( - `Event does not mark the creation or completion of an entity. Skipping event!` + let dbEventType; + try { + dbEventType = await dbEvent.getChainEventType(); + if (!dbEventType) { + log.error(`Failed to fetch event type! Ignoring.`); + return; + } + } catch (e) { + log.error( + `Failed to get chain-event type for event: ${JSON.stringify(event)}` ); return dbEvent; } + const formattedEvent: ChainEventAttributes = dbEvent.toJSON(); + formattedEvent.ChainEventType = dbEventType.toJSON(); + const publishData: RmqCENotificationCUD.RmqMsgType = { - ChainEvent: dbEvent.toJSON(), + ChainEvent: formattedEvent, event, cud: 'create', }; diff --git a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/storage.ts b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/storage.ts index e1cd27f04e7..530fd26f631 100644 --- a/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/storage.ts +++ b/packages/chain-events/services/ChainEventsConsumer/ChainEventHandlers/storage.ts @@ -2,7 +2,10 @@ * Generic handler that stores the event in the database. */ import { addPrefix, factory } from 'common-common/src/logging'; -import type { RabbitMQController } from 'common-common/src/rabbitmq'; +import type { + RabbitMQController, + RmqCETypeCUD, +} from 'common-common/src/rabbitmq'; import { RascalPublications } from 'common-common/src/rabbitmq'; import NodeCache from 'node-cache'; import hash from 'object-hash'; @@ -79,11 +82,51 @@ export default class extends IEventHandler { return; } + // locate event type and add event (and event type if needed) to database + const [dbEventType, created] = + await this._models.ChainEventType.findOrCreate({ + where: { + id: `${chain}-${event.data.kind.toString()}`, + chain, + event_network: event.network, + event_name: event.data.kind.toString(), + }, + }); + + if (created) { + const publishData: RmqCETypeCUD.RmqMsgType = { + chainEventTypeId: dbEventType.id, + cud: 'create', + }; + + await this._rmqController.safePublish( + publishData, + dbEventType.id, + RascalPublications.ChainEventTypeCUDMain, + { + sequelize: this._models.sequelize, + model: this._models.ChainEventType, + } + ); + + log.info(`STORAGE HANDLER MESSAGE PUBLISHED`); + } + + if (!dbEventType) { + log.error(`unknown event type: ${event.data.kind}`); + return; + } else { + if (created) { + log.info(`Created new ChainEventType: ${dbEventType.id}`); + } else { + log.trace(`found chain event type: ${dbEventType.id}`); + } + } + const eventData = { + chain_event_type_id: dbEventType.id, block_number: event.blockNumber, event_data: event.data, - network: event.network, - chain, }; // duplicate event check @@ -94,6 +137,8 @@ export default class extends IEventHandler { if (!cachedEvent) { const dbEvent = await this._models.ChainEvent.create(eventData); + // populate chainEventType, so we don't need to re-populate it in subsequence handlers + dbEvent.ChainEventType = dbEventType; // no need to save the entire event data since the key is the hash of the data this.eventCache.set(eventKey, true); diff --git a/packages/chain-events/services/ChainEventsConsumer/chainEventsConsumer.ts b/packages/chain-events/services/ChainEventsConsumer/chainEventsConsumer.ts index 9fd3927769f..67b3178723a 100644 --- a/packages/chain-events/services/ChainEventsConsumer/chainEventsConsumer.ts +++ b/packages/chain-events/services/ChainEventsConsumer/chainEventsConsumer.ts @@ -78,8 +78,8 @@ export async function setupChainEventConsumer(): Promise { // WARNING: due to dbEvent in each handler ORDER OF HANDLERS MATTERS! const allChainEventHandlers = [ storageHandler, - entityArchivalHandler, notificationsHandler, + entityArchivalHandler, ]; // setup Chain diff --git a/packages/chain-events/services/ChainEventsConsumer/republishMessages.ts b/packages/chain-events/services/ChainEventsConsumer/republishMessages.ts new file mode 100644 index 00000000000..172f687e0a6 --- /dev/null +++ b/packages/chain-events/services/ChainEventsConsumer/republishMessages.ts @@ -0,0 +1,52 @@ +import type { + RabbitMQController, + RmqCETypeCUD, +} from 'common-common/src/rabbitmq'; +import { + RascalPublications, + RepublishFailedMessages, +} from 'common-common/src/rabbitmq'; +import * as Sequelize from 'sequelize'; + +import type { DB } from '../database/database'; + +/** + * A worker that periodically republishes data from the database if it's queued value is between -1 and 5. A queued + * value of -1 + * + */ +export class RepublishMessages extends RepublishFailedMessages { + constructor(_rmqController: RabbitMQController, _models: DB) { + super(_rmqController, _models, 180000); + } + + protected async job(): Promise { + const result = await this._models.ChainEventType.findAll({ + where: { + queued: { + [Sequelize.Op.between]: [-1, 5], + }, + }, + }); + + // TODO + // if (result.length > 100) {} + + for (const eventType of result) { + const publishData: RmqCETypeCUD.RmqMsgType = { + chainEventTypeId: eventType.id, + cud: 'create', + }; + + await this._rmqController.safePublish( + publishData, + eventType.id, + RascalPublications.ChainEventTypeCUDMain, + { + sequelize: this._models.sequelize, + model: this._models.ChainEventType, + } + ); + } + } +} diff --git a/packages/chain-events/services/ChainSubscriber/util.ts b/packages/chain-events/services/ChainSubscriber/util.ts index e398aed4ffb..cfc40a44d25 100644 --- a/packages/chain-events/services/ChainSubscriber/util.ts +++ b/packages/chain-events/services/ChainSubscriber/util.ts @@ -8,7 +8,6 @@ import type { SubstrateEvents } from '../../src'; import { createListener, ErcLoggingHandler, - getChainEventNetwork, LoggingHandler, SupportedNetwork, } from '../../src'; @@ -219,15 +218,15 @@ async function setupNewListeners( ) { for (const chain of newChains) { let network: SupportedNetwork; - try { - network = getChainEventNetwork(chain.network, chain.base); - } catch (e) { - log.error( - `Unknown chain base: ${chain.base} \tand network: ${chain.network}`, - e - ); - continue; - } + if (chain.base === ChainBase.Substrate) + network = SupportedNetwork.Substrate; + else if (chain.base === ChainBase.CosmosSDK) + network = SupportedNetwork.Cosmos; + else if (chain.network === ChainNetwork.Compound) + network = SupportedNetwork.Compound; + else if (chain.network === ChainNetwork.Aave) + network = SupportedNetwork.Aave; + try { log.info(`Starting listener for: ${chain.id}`); listenerInstances[chain.id] = await createListener(chain.id, network, { @@ -380,8 +379,21 @@ export function getListenerNames( async function discoverReconnectRange(this: DB, chain: string) { let latestBlock; try { + const eventTypes = ( + await this.ChainEventType.findAll({ + where: { chain }, + }) + ).map((x) => x.id); + + if (eventTypes.length === 0) { + log.info(`[${chain}]: No event types exist in the database`); + return { startBlock: null }; + } + latestBlock = await this.ChainEvent.max('block_number', { - where: { chain }, + where: { + chain_event_type_id: eventTypes, + }, }); if (latestBlock) { diff --git a/packages/chain-events/services/app/routes/entities.ts b/packages/chain-events/services/app/routes/entities.ts index 8b5ae3a3363..8bc45eb5e26 100644 --- a/packages/chain-events/services/app/routes/entities.ts +++ b/packages/chain-events/services/app/routes/entities.ts @@ -22,6 +22,7 @@ const entities: any = async ( { model: models.ChainEvent, order: [[models.ChainEvent, 'id', 'asc']], + include: [models.ChainEventType], }, ], order: [['created_at', 'DESC']], diff --git a/packages/chain-events/services/app/routes/eventActivity.ts b/packages/chain-events/services/app/routes/eventActivity.ts index 91fc2f8f9f2..14d2ede2298 100644 --- a/packages/chain-events/services/app/routes/eventActivity.ts +++ b/packages/chain-events/services/app/routes/eventActivity.ts @@ -1,5 +1,6 @@ import type { NextFunction, Request, Response } from 'express'; import { AppError } from 'common-common/src/errors'; +import { QueryTypes } from 'sequelize'; import type { DB } from '../../database/database'; @@ -17,10 +18,24 @@ const eventActivity: any = async ( return next(new AppError(Errors.NeedLimit)); } - const events = await models.ChainEvent.findAll({ - order: [['created_at', 'DESC']], - limit: req.query.limit, - }); + const events = await models.sequelize.query( + ` + SELECT ce.id, + ce.chain_event_type_id, + ce.block_number, + ce.event_data, + ce.created_at, + ce.updated_at, + ce.entity_id, + cet.chain, + cet.event_network + FROM "ChainEvents" ce + JOIN "ChainEventTypes" cet ON ce.chain_event_type_id = cet.id + ORDER BY ce.created_at DESC + LIMIT ?; + `, + { replacements: [req.query.limit], raw: true, type: QueryTypes.SELECT } + ); return res.json({ status: 'Success', result: events }); }; diff --git a/packages/chain-events/services/database/database.ts b/packages/chain-events/services/database/database.ts index 0c3b3339c34..a7ca48f25d0 100644 --- a/packages/chain-events/services/database/database.ts +++ b/packages/chain-events/services/database/database.ts @@ -6,6 +6,8 @@ import type { ChainEntityModelStatic } from './models/chain_entity'; import ChainEntityFactory from './models/chain_entity'; import type { ChainEventModelStatic } from './models/chain_event'; import ChainEventFactory from './models/chain_event'; +import type { ChainEventTypeModelStatic } from './models/chain_event_type'; +import ChainEventTypeFactory from './models/chain_event_type'; import { factory, formatFilename } from 'common-common/src/logging'; const log = factory.getLogger(formatFilename(__filename)); @@ -13,6 +15,7 @@ const log = factory.getLogger(formatFilename(__filename)); export type Models = { ChainEntity: ChainEntityModelStatic; ChainEvent: ChainEventModelStatic; + ChainEventType: ChainEventTypeModelStatic; }; export interface DB extends Models { @@ -49,6 +52,7 @@ export const sequelize = new Sequelize(DATABASE_URI, { const models: Models = { ChainEntity: ChainEntityFactory(sequelize, DataTypes), ChainEvent: ChainEventFactory(sequelize, DataTypes), + ChainEventType: ChainEventTypeFactory(sequelize, DataTypes), }; const db: DB = { diff --git a/packages/chain-events/services/database/migrations/20221206094622-move-chain-to-event.js b/packages/chain-events/services/database/migrations/20221206094622-move-chain-to-event.js deleted file mode 100644 index 59a3b1e23c5..00000000000 --- a/packages/chain-events/services/database/migrations/20221206094622-move-chain-to-event.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.sequelize.transaction(async (t) => { - await queryInterface.addColumn( - 'ChainEvents', - 'chain', - { - type: Sequelize.STRING, - allowNull: true, - }, - { transaction: t } - ); - await queryInterface.sequelize.query( - ` - UPDATE "ChainEvents" CE - SET chain = CET.chain - FROM "ChainEventTypes" CET - WHERE CE.chain_event_type_id = CET.id; - `, - { transaction: t } - ); - - await queryInterface.changeColumn( - 'ChainEvents', - 'chain', - { - type: Sequelize.STRING, - allowNull: false, - }, - { transaction: t } - ); - }); - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('ChainEvents', 'chain'); - }, -}; diff --git a/packages/chain-events/services/database/migrations/20221206100637-move-network-to-event.js b/packages/chain-events/services/database/migrations/20221206100637-move-network-to-event.js deleted file mode 100644 index fb5808b349c..00000000000 --- a/packages/chain-events/services/database/migrations/20221206100637-move-network-to-event.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.sequelize.transaction(async (t) => { - await queryInterface.sequelize.query( - ` - UPDATE "ChainEvents" CE - SET network = CET.event_network - FROM "ChainEventTypes" CET - WHERE CE.chain_event_type_id = CET.id; - `, - { transaction: t } - ); - - await queryInterface.changeColumn( - 'ChainEvents', - 'network', - { - type: Sequelize.STRING, - allowNull: false, - }, - { transaction: t } - ); - }); - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('ChainEvents', 'network'); - }, -}; diff --git a/packages/chain-events/services/database/migrations/20221221220946-remove-chain-event-types.js b/packages/chain-events/services/database/migrations/20221221220946-remove-chain-event-types.js deleted file mode 100644 index 0d8fa60bba0..00000000000 --- a/packages/chain-events/services/database/migrations/20221221220946-remove-chain-event-types.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.sequelize.transaction(async (t) => { - await queryInterface.removeColumn('ChainEvents', 'chain_event_type_id', { - transaction: t, - }); - await queryInterface.dropTable('ChainEventTypes', { transaction: t }); - }); - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.sequelize.transaction(async (t) => { - await queryInterface.createTable( - 'ChainEventTypes', - { - // id = chain-event_name (event_name is value of string enum) - id: { type: Sequelize.STRING, primaryKey: true }, - chain: { type: Sequelize.STRING, allowNull: false }, - // should never be null, but added here for migration purposes - event_network: { type: Sequelize.STRING, allowNull: true }, - event_name: { type: Sequelize.STRING, allowNull: false }, - queued: { - type: Sequelize.SMALLINT, - allowNull: false, - defaultValue: 0, - }, - }, - { transaction: t } - ); - - await queryInterface.addColumn( - 'ChainEvents', - 'chain_event_type_id', - { - type: Sequelize.STRING, - allowNull: true, - references: { model: 'ChainEventTypes', key: 'id' }, - }, - { transaction: t } - ); - }); - }, -}; diff --git a/packages/chain-events/services/database/models/chain_entity.ts b/packages/chain-events/services/database/models/chain_entity.ts index 6cd71a80b56..24dd00f45ed 100644 --- a/packages/chain-events/services/database/models/chain_entity.ts +++ b/packages/chain-events/services/database/models/chain_entity.ts @@ -1,7 +1,7 @@ import type * as Sequelize from 'sequelize'; import type { DataTypes } from 'sequelize'; -import type { ChainEventAttributes, ChainEventInstance } from './chain_event'; +import type { ChainEventAttributes } from './chain_event'; import type { ModelStatic, ModelInstance } from './types'; export type ChainEntityAttributes = { @@ -18,9 +18,7 @@ export type ChainEntityAttributes = { ChainEvents?: ChainEventAttributes[]; }; -export type ChainEntityInstance = ModelInstance & { - getChainEvents: Sequelize.HasManyGetAssociationsMixin; -}; +export type ChainEntityInstance = ModelInstance; export type ChainEntityModelStatic = ModelStatic; diff --git a/packages/chain-events/services/database/models/chain_event.ts b/packages/chain-events/services/database/models/chain_event.ts index dd69cd9745b..7ba1827c13e 100644 --- a/packages/chain-events/services/database/models/chain_event.ts +++ b/packages/chain-events/services/database/models/chain_event.ts @@ -3,27 +3,27 @@ import type { DataTypes } from 'sequelize'; import type { ModelStatic, ModelInstance } from './types'; import type { - ChainEntityAttributes, - ChainEntityInstance, -} from './chain_entity'; -import type { SupportedNetwork } from '../../../src'; + ChainEventTypeAttributes, + ChainEventTypeInstance, +} from './chain_event_type'; +import type { ChainEntityAttributes } from './chain_entity'; export type ChainEventAttributes = { id: number; + chain_event_type_id: string; block_number: number; event_data: any; queued: number; entity_id?: number; - network: SupportedNetwork; - chain: string; created_at?: Date; updated_at?: Date; + ChainEventType?: ChainEventTypeAttributes; ChainEntity?: ChainEntityAttributes; }; export type ChainEventInstance = ModelInstance & { - getChainEntity: Sequelize.HasOneGetAssociationMixin; + getChainEventType: Sequelize.HasOneGetAssociationMixin; }; export type ChainEventModelStatic = ModelStatic; @@ -36,14 +36,13 @@ export default ( 'ChainEvent', { id: { type: dataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + chain_event_type_id: { type: dataTypes.STRING, allowNull: false }, block_number: { type: dataTypes.INTEGER, allowNull: false }, entity_id: { type: dataTypes.INTEGER, allowNull: true }, event_data: { type: dataTypes.JSONB, allowNull: false }, created_at: { type: dataTypes.DATE, allowNull: false }, updated_at: { type: dataTypes.DATE, allowNull: false }, queued: { type: dataTypes.SMALLINT, allowNull: false, defaultValue: 0 }, - chain: { type: dataTypes.STRING, allowNull: false }, - network: { type: dataTypes.STRING, allowNull: false }, }, { tableName: 'ChainEvents', @@ -60,6 +59,11 @@ export default ( ); ChainEvent.associate = (models) => { + // master event type + models.ChainEvent.belongsTo(models.ChainEventType, { + foreignKey: 'chain_event_type_id', + targetKey: 'id', + }); models.ChainEvent.belongsTo(models.ChainEntity, { foreignKey: 'entity_id', targetKey: 'id', diff --git a/packages/chain-events/services/database/models/chain_event_type.ts b/packages/chain-events/services/database/models/chain_event_type.ts new file mode 100644 index 00000000000..940fcccc448 --- /dev/null +++ b/packages/chain-events/services/database/models/chain_event_type.ts @@ -0,0 +1,49 @@ +import type * as Sequelize from 'sequelize'; +import type { DataTypes } from 'sequelize'; + +import type { ChainEventAttributes } from './chain_event'; +import type { ModelStatic, ModelInstance } from './types'; + +export type ChainEventTypeAttributes = { + id: string; + chain: string; + event_network: string; + event_name: string; + queued: number; + ChainEvents?: ChainEventAttributes[]; +}; + +export type ChainEventTypeInstance = ModelInstance; + +export type ChainEventTypeModelStatic = ModelStatic; + +export default ( + sequelize: Sequelize.Sequelize, + dataTypes: typeof DataTypes +): ChainEventTypeModelStatic => { + const ChainEventType = sequelize.define( + 'ChainEventType', + { + // id = chain-event_name (event_name is value of string enum) + id: { type: dataTypes.STRING, primaryKey: true }, + chain: { type: dataTypes.STRING, allowNull: false }, + // should never be null, but added here for migration purposes + event_network: { type: dataTypes.STRING, allowNull: true }, + event_name: { type: dataTypes.STRING, allowNull: false }, + queued: { type: dataTypes.SMALLINT, allowNull: false, defaultValue: 0 }, + }, + { + tableName: 'ChainEventTypes', + timestamps: false, + underscored: true, + indexes: [{ fields: ['id'] }, { fields: ['chain', 'event_name'] }], + } + ); + + ChainEventType.associate = (models) => { + // many emitted events of this type + models.ChainEventType.hasMany(models.ChainEvent, { as: 'events' }); + }; + + return ChainEventType; +}; diff --git a/packages/chain-events/src/interfaces.ts b/packages/chain-events/src/interfaces.ts index fac21c0e542..4a06943a83b 100644 --- a/packages/chain-events/src/interfaces.ts +++ b/packages/chain-events/src/interfaces.ts @@ -19,7 +19,6 @@ import type { Api as CompoundApi } from './chains/compound/types'; import type { Api as CommonwealthApi } from './chains/commonwealth/types'; import type { Api as AaveApi } from './chains/aave/types'; import type { Listener } from './Listener'; -import { ChainBase, ChainNetwork } from 'common-common/src/types'; // add other events here as union types export type IChainEntityKind = @@ -206,27 +205,6 @@ export interface IEventTitle { export type TitlerFilter = (kind: IChainEventKind) => IEventTitle; -/** - * This function takes the network and base attribute of our current chains model and returns the relevant chain-event - * network. The chain-event network is an instance of SupportedNetwork enum. This is NOT the same as the network on - * the commonwealth chains model. This function is useful for determining which SupportedNetwork listener a - * Commonwealth 'chain' should use. Throws if the given chainNetwork and chainBase don't match a chain-event network - * i.e. SupportedNetwork. - * @param chainNetwork The network attribute of the Commonwealth chain model - * @param chainBase The base attribute of the Commonwealth chain model - */ -export function getChainEventNetwork( - chainNetwork: string, - chainBase: string -): SupportedNetwork { - if (chainBase === ChainBase.Substrate) return SupportedNetwork.Substrate; - else if (chainBase === ChainBase.CosmosSDK) return SupportedNetwork.Cosmos; - else if (chainNetwork === ChainNetwork.Compound) - return SupportedNetwork.Compound; - else if (chainNetwork === ChainNetwork.Aave) return SupportedNetwork.Aave; - else throw new Error('No matching SupportedNetwork'); -} - /** * Returns the key of the value that is unique to the entities chain and type i.e. the key whose associated value * becomes the type_id of the chain-entity. The combination of chain, type, and type_id must/will always be unique. diff --git a/packages/chain-events/test/integration/service-tests/chainEventsConsumer.test.ts b/packages/chain-events/test/integration/service-tests/chainEventsConsumer.test.ts index 56731b0510d..7f7b9819425 100644 --- a/packages/chain-events/test/integration/service-tests/chainEventsConsumer.test.ts +++ b/packages/chain-events/test/integration/service-tests/chainEventsConsumer.test.ts @@ -4,6 +4,7 @@ import { RascalQueues, RascalRoutingKeys, RmqCENotificationCUD, + RmqCETypeCUD, RmqEntityCUD, } from 'common-common/src/rabbitmq'; import type { ServiceConsumer } from 'common-common/src/serviceConsumer'; @@ -184,6 +185,21 @@ describe('Tests for the ChainEventsConsumer service', () => { expect(dbResult).to.not.be.null; + // check whether the new chain-event-type was pushed to the appropriate queue + const message = await getRmqMessage( + RABBITMQ_API_URI, + RascalQueues.ChainEventTypeCUDMain, + false + ); + expect(message).to.have.property('length'); + expect(message.length).to.equal(1); + expect(JSON.parse(message[0].payload)).to.deep.equal({ + chainEventTypeId: 'random-chain-transfer', + cud: 'create', + }); + expect(RmqCETypeCUD.isValidMsgFormat(JSON.parse(message[0].payload))).to.be + .true; + await models.ChainEvent.destroy({ where: { block_number: chainEvent.blockNumber, @@ -196,6 +212,10 @@ describe('Tests for the ChainEventsConsumer service', () => { }, }, }); + + await models.ChainEventType.destroy({ + where: { id: `${chain}-${ceData.kind}` }, + }); }); it('Should push new chain-events to the chain-event notifications queue', async () => { diff --git a/packages/common-common/src/rabbitmq/rabbitMQConfig.ts b/packages/common-common/src/rabbitmq/rabbitMQConfig.ts index 7c8e94501e5..3e4dfdc4c70 100644 --- a/packages/common-common/src/rabbitmq/rabbitMQConfig.ts +++ b/packages/common-common/src/rabbitmq/rabbitMQConfig.ts @@ -117,6 +117,10 @@ export function getRabbitMQConfig(rabbitmq_uri: string): Rascal.BrokerConfig { 'x-message-ttl': 600000, }, }, + [RascalQueues.ChainEventTypeCUDMain]: { + ...queueConfig, + options: queueOptions, + }, [RascalQueues.SnapshotProposalNotifications]: { ...queueConfig, options: { @@ -157,6 +161,12 @@ export function getRabbitMQConfig(rabbitmq_uri: string): Rascal.BrokerConfig { destinationType: 'queue', bindingKey: RascalBindings.ChainEventNotifications, }, + [RascalBindings.ChainEventType]: { + source: RascalExchanges.CUD, + destination: RascalQueues.ChainEventTypeCUDMain, + destinationType: 'queue', + bindingKey: RascalRoutingKeys.ChainEventTypeCUD, + }, [RascalBindings.SnapshotProposalNotifications]: { source: RascalExchanges.Notifications, destination: RascalQueues.SnapshotProposalNotifications, @@ -197,6 +207,11 @@ export function getRabbitMQConfig(rabbitmq_uri: string): Rascal.BrokerConfig { routingKey: RascalRoutingKeys.ChainEventNotifications, ...publicationConfig, }, + [RascalPublications.ChainEventTypeCUDMain]: { + exchange: RascalExchanges.CUD, + routingKey: RascalRoutingKeys.ChainEventTypeCUD, + ...publicationConfig, + }, [RascalPublications.SnapshotProposalNotifications]: { exchange: RascalExchanges.Notifications, routingKey: RascalRoutingKeys.SnapshotProposalNotifications, @@ -225,6 +240,10 @@ export function getRabbitMQConfig(rabbitmq_uri: string): Rascal.BrokerConfig { queue: RascalQueues.ChainEventNotifications, ...subscriptionConfig, }, + [RascalSubscriptions.ChainEventTypeCUDMain]: { + queue: RascalQueues.ChainEventTypeCUDMain, + ...subscriptionConfig, + }, [RascalSubscriptions.SnapshotProposalNotifications]: { queue: RascalQueues.SnapshotProposalNotifications, ...subscriptionConfig, diff --git a/packages/common-common/src/rabbitmq/rabbitMQController.ts b/packages/common-common/src/rabbitmq/rabbitMQController.ts index e3a8fc99ff5..b4be0f3f28e 100644 --- a/packages/common-common/src/rabbitmq/rabbitMQController.ts +++ b/packages/common-common/src/rabbitmq/rabbitMQController.ts @@ -9,13 +9,15 @@ import type Rollbar from 'rollbar'; import type { Sequelize } from 'sequelize'; import type { ChainEntityModelStatic } from 'chain-events/services/database/models/chain_entity'; import type { ChainEventModelStatic } from 'chain-events/services/database/models/chain_event'; +import type { ChainEventTypeModelStatic } from 'chain-events/services/database/models/chain_event_type'; import { factory, formatFilename } from 'common-common/src/logging'; const log = factory.getLogger(formatFilename(__filename)); export type SafeRmqPublishSupported = | ChainEntityModelStatic - | ChainEventModelStatic; + | ChainEventModelStatic + | ChainEventTypeModelStatic; export class RabbitMQControllerError extends Error { constructor(msg: string) { diff --git a/packages/common-common/src/rabbitmq/types/chainEventNotificationsCUD.ts b/packages/common-common/src/rabbitmq/types/chainEventNotificationsCUD.ts index a8e5359ce2c..90792696438 100644 --- a/packages/common-common/src/rabbitmq/types/chainEventNotificationsCUD.ts +++ b/packages/common-common/src/rabbitmq/types/chainEventNotificationsCUD.ts @@ -23,16 +23,19 @@ export const RmqCENotificationCUD: RmqMsgNamespace = { + getInvalidFormatError(chainEventType: any): RmqMsgFormatError { + return new RmqMsgFormatError( + `The following chain-event-type is improperly formatted: ${JSON.stringify( + chainEventType + )}` + ); + }, + + isValidMsgFormat(data: any): data is IRmqMsgCreateCETypeCUD { + return !!( + data.chainEventTypeId && + typeof data.chainEventTypeId === 'string' && + data.cud === 'create' + ); + }, + + checkMsgFormat(data: any): void { + const valid = this.isValidMsgFormat(data); + if (!valid) { + console.log( + `The following chain-event-type is improperly formatted: ${JSON.stringify( + data + )}` + ); + throw this.getInvalidFormatError(data); + } + }, +}; + +export namespace RmqCETypeCUD { + export type RmqMsgType = IRmqMsgCreateCETypeCUD; +} diff --git a/packages/common-common/src/rabbitmq/types/index.ts b/packages/common-common/src/rabbitmq/types/index.ts index 573bf664049..f9ce9e90b6c 100644 --- a/packages/common-common/src/rabbitmq/types/index.ts +++ b/packages/common-common/src/rabbitmq/types/index.ts @@ -1,8 +1,10 @@ import type { RmqEntityCUD } from './chainEntityCUD'; import type { RmqCENotificationCUD } from './chainEventNotificationsCUD'; +import type { RmqCETypeCUD } from './chainEventTypeCUD'; export * from './chainEntityCUD'; export * from './chainEventNotificationsCUD'; +export * from './chainEventTypeCUD'; export * from './chainEvents'; export * from './chainEventNotification'; @@ -28,6 +30,7 @@ export class RmqMsgFormatError extends Error { export type TRmqMessages = | RmqEntityCUD.RmqMsgType | RmqCENotificationCUD.RmqMsgType + | RmqCETypeCUD.RmqMsgType | RmqCWEvent.RmqMsgType | RmqCENotification.RmqMsgType | RmqSnapshotEvent.RmqMsgType @@ -44,6 +47,7 @@ export enum RascalPublications { ChainEntityCUDMain = 'ChainEntityCUDMainPublication', ChainEventNotificationsCUDMain = 'ChainEventNotificationsCUDMainPublication', ChainEventNotifications = 'ChainEventNotificationsPublication', + ChainEventTypeCUDMain = 'ChainEventTypeCUDMainPublication', SnapshotProposalNotifications = 'SnapshotProposalNotificationsPublication', SnapshotListener = 'SnapshotListenerPublication', } @@ -53,6 +57,7 @@ export enum RascalSubscriptions { ChainEntityCUDMain = 'ChainEntityCUDMainSubscription', ChainEventNotificationsCUDMain = 'ChainEventNotificationsCUDSubscription', ChainEventNotifications = 'ChainEventNotificationsSubscription', + ChainEventTypeCUDMain = 'ChainEventTypeCUDMainSubscription', SnapshotProposalNotifications = 'SnapshotProposalNotificationsSubscription', SnapshotListener = 'SnapshotListenerSubscription', } @@ -70,6 +75,7 @@ export enum RascalQueues { ChainEntityCUDMain = 'ChainEntityCUDMainQueue', ChainEventNotificationsCUDMain = 'ChainEventNotificationsCUDMainQueue', ChainEventNotifications = 'ChainEventNotificationsQueue', + ChainEventTypeCUDMain = 'ChainEventTypeCUDMainQueue', DeadLetter = 'DeadLetterQueue', SnapshotProposalNotifications = 'SnapshotProposalNotificationsQueue', SnapshotListener = 'SnapshotListenerQueue', @@ -80,6 +86,7 @@ export enum RascalBindings { ChainEntityCUDMain = 'ChainEntityCUDMainBinding', ChainEventNotificationsCUD = 'ChainEventNotificationsCUDBinding', ChainEventNotifications = 'ChainEventNotificationsBinding', + ChainEventType = 'ChainEventTypeBinding', SnapshotProposalNotifications = 'SnapshotProposalNotificationsBinding', SnapshotListener = 'SnapshotListenerBinding', DeadLetter = 'DeadLetterBinding', @@ -90,6 +97,7 @@ export enum RascalRoutingKeys { ChainEntityCUD = 'ChainEntityCUD', ChainEventNotificationsCUD = 'ChainEventNotificationsCUD', ChainEventNotifications = 'ChainEventNotifications', + ChainEventTypeCUD = 'ChainEventTypeCUD', SnapshotProposalNotifications = 'SnapshotProposalNotifications', SnapshotListener = 'SnapshotListener', DeadLetter = 'DeadLetter', diff --git a/packages/commonwealth/client/scripts/controllers/server/chain_entities.ts b/packages/commonwealth/client/scripts/controllers/server/chain_entities.ts index 7159917c3af..c3b802b0fe0 100644 --- a/packages/commonwealth/client/scripts/controllers/server/chain_entities.ts +++ b/packages/commonwealth/client/scripts/controllers/server/chain_entities.ts @@ -13,11 +13,10 @@ import { import { SubstrateTypes } from 'chain-events/src/types'; import type { ProposalType } from 'common-common/src/types'; import { ChainBase, ChainNetwork } from 'common-common/src/types'; -import getFetch from 'helpers/getFetch'; +import { getBaseUrl, getFetch } from 'helpers/getUrl'; import type { ChainInfo } from 'models'; -import { ChainEntity, ChainEvent } from 'models'; +import { ChainEntity, ChainEvent, ChainEventType } from 'models'; import { proposalSlugToChainEntityType } from '../../identifiers'; -import app from 'state'; export function chainToEventNetwork(c: ChainInfo): SupportedNetwork { if (c.base === ChainBase.Substrate) return SupportedNetwork.Substrate; @@ -96,8 +95,8 @@ class ChainEntityController { // load the chain-entity objects const [entities, entityMetas] = await Promise.all([ - getFetch(`${app.serverUrl()}/ce/entities`, options), - getFetch(`${app.serverUrl()}/getEntityMeta`, options), + getFetch(getBaseUrl() + '/entities', options), + getFetch(getBaseUrl() + '/getEntityMeta', options), ]); const data = []; @@ -128,9 +127,7 @@ class ChainEntityController { } public async getRawEntities(chain: string): Promise { - const entities = await getFetch(`${app.serverUrl()}/ce/entities`, { - chain, - }); + const entities = await getFetch(getBaseUrl() + '/entities', { chain }); const data = []; if (Array.isArray(entities)) { for (const entityJSON of entities) { @@ -174,9 +171,20 @@ class ChainEntityController { // eslint-disable-next-line no-continue if (!eventEntity) continue; const [entityKind] = eventEntity; + // create event type + const eventType = new ChainEventType( + `${chain}-${cwEvent.data.kind.toString()}`, + chain, + network, + cwEvent.data.kind.toString() + ); // create event - const event = new ChainEvent(cwEvent.blockNumber, cwEvent.data); + const event = new ChainEvent( + cwEvent.blockNumber, + cwEvent.data, + eventType + ); // create entity const fieldName = getUniqueEntityKey(network, entityKind); diff --git a/packages/commonwealth/client/scripts/controllers/server/notifications.ts b/packages/commonwealth/client/scripts/controllers/server/notifications.ts index 643117e2497..bf70ed35a1c 100644 --- a/packages/commonwealth/client/scripts/controllers/server/notifications.ts +++ b/packages/commonwealth/client/scripts/controllers/server/notifications.ts @@ -1,7 +1,7 @@ /* eslint-disable no-restricted-syntax */ import $ from 'jquery'; import m from 'mithril'; -import { Notification, NotificationSubscription } from 'models'; +import { ChainEventType, Notification, NotificationSubscription } from 'models'; import { modelFromServer } from 'models/NotificationSubscription'; import app from 'state'; @@ -42,9 +42,6 @@ interface NotifOptions { class NotificationsController { private _discussionStore: NotificationStore = new NotificationStore(); private _chainEventStore: NotificationStore = new NotificationStore(); - // these are the chains that chain-events has active listeners for (used to detemine what chains are shown on the - // notification settings page - private _chainEventSubscribedChainIds: string[] = []; private _maxChainEventNotificationId: number = Number.POSITIVE_INFINITY; private _maxDiscussionNotificationId: number = Number.POSITIVE_INFINITY; @@ -52,10 +49,6 @@ class NotificationsController { private _numPages = 0; private _numUnread = 0; - public get chainEventSubscribedChainIds(): string[] { - return this._chainEventSubscribedChainIds; - } - public get numPages(): number { return this._numPages; } @@ -90,6 +83,7 @@ class NotificationsController { if (subscription) { return this.enableSubscriptions([subscription]); } else { + // TODO: Change to POST /subscription return post( '/createSubscription', { @@ -98,7 +92,7 @@ class NotificationsController { is_active: true, }, (result) => { - const newSubscription = modelFromServer(result); + const newSubscription = NotificationSubscription.fromJSON(result); if (newSubscription.category === 'chain-event') app.socket.chainEventsNs.addChainEventSubscriptions([ newSubscription, @@ -341,13 +335,25 @@ class NotificationsController { for (const subscriptionJSON of subscriptions) { const subscription = NotificationSubscription.fromJSON(subscriptionJSON); + // save the chainEventType for the subscription if the subscription type is chain-event + let chainEventType = null; + if (subscriptionJSON.ChainEventType) { + chainEventType = ChainEventType.fromJSON( + subscriptionJSON.ChainEventType + ); + } + // save the notification read + notification instances if any for (const notificationsReadJSON of subscriptionJSON.NotificationsReads) { const data = { is_read: notificationsReadJSON.is_read, ...notificationsReadJSON.Notification, }; - const notification = Notification.fromJSON(data, subscription); + const notification = Notification.fromJSON( + data, + subscription, + chainEventType + ); if (subscription.category === 'chain-event') { if (!this._chainEventStore.getById(notification.id)) @@ -382,18 +388,11 @@ class NotificationsController { }); } - public getSubscribedChains() { - return post('/getSubscribedChains', {}, (result) => { - this._chainEventSubscribedChainIds = result.map((x) => x.id); - }); - } - public async refresh() { return Promise.all([ this.getDiscussionNotifications(), this.getChainEventNotifications(), this.getSubscriptions(), - this.getSubscribedChains(), ]); } } diff --git a/packages/commonwealth/client/scripts/controllers/server/socket/chainEventsNs.ts b/packages/commonwealth/client/scripts/controllers/server/socket/chainEventsNs.ts index 691dc587528..f7229cac6d1 100644 --- a/packages/commonwealth/client/scripts/controllers/server/socket/chainEventsNs.ts +++ b/packages/commonwealth/client/scripts/controllers/server/socket/chainEventsNs.ts @@ -25,12 +25,12 @@ export class ChainEventsNamespace { public addChainEventSubscriptions(subs: NotificationSubscription[]) { if (this._isConnected) { + const eventTypes = subs.filter((x) => !!x.chainEventTypeId); const roomsToJoin = []; - for (const sub of subs) { - const chain = sub?.Chain?.id || sub.Chain; - if (!this.subscriptionRoomsJoined.has(chain)) { - roomsToJoin.push(chain); - this.subscriptionRoomsJoined.add(chain); + for (const eventType of eventTypes) { + if (!this.subscriptionRoomsJoined.has(eventType)) { + roomsToJoin.push(eventType); + this.subscriptionRoomsJoined.add(eventType); } } if (roomsToJoin.length > 0) { @@ -44,12 +44,12 @@ export class ChainEventsNamespace { public deleteChainEventSubscriptions(subs: NotificationSubscription[]) { if (this._isConnected) { + const eventTypes = subs.filter((x) => !!x.chainEventTypeId); const roomsToLeave = []; - for (const sub of subs) { - const chain = sub?.Chain?.id || sub.Chain; - if (this.subscriptionRoomsJoined.has(chain)) { - roomsToLeave.push(chain); - this.subscriptionRoomsJoined.delete(chain); + for (const eventType of eventTypes) { + if (this.subscriptionRoomsJoined.has(eventType)) { + roomsToLeave.push(eventType); + this.subscriptionRoomsJoined.delete(eventType); } } @@ -64,7 +64,8 @@ export class ChainEventsNamespace { private onChainEvent(notification: ChainEventNotification) { const subscription = app.user.notifications.subscriptions.find( - (sub) => sub.getChain === notification.ChainEvent.chain + (sub) => + sub.chainEventTypeId === notification.ChainEvent.ChainEventType.id ); if (!subscription) { // will theoretically never happen as subscriptions are added/removed on Socket.io as they happen locally diff --git a/packages/commonwealth/client/scripts/helpers/getFetch.ts b/packages/commonwealth/client/scripts/helpers/getFetch.ts deleted file mode 100644 index 7c8a6ede464..00000000000 --- a/packages/commonwealth/client/scripts/helpers/getFetch.ts +++ /dev/null @@ -1,23 +0,0 @@ -export default async function getFetch( - url: string, - queryParams?: { [key: string]: any } -) { - let queryUrl; - if (queryParams) queryUrl = url + '?' + new URLSearchParams(queryParams); - try { - const response = await fetch(queryUrl || url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (response.ok) { - const { result } = await response.json(); - return result; - } else { - console.error(`Fetch to ${queryUrl} failed, `, response); - } - } catch (e) { - console.error(e); - } -} diff --git a/packages/commonwealth/client/scripts/helpers/getUrl.ts b/packages/commonwealth/client/scripts/helpers/getUrl.ts new file mode 100644 index 00000000000..39e5b095332 --- /dev/null +++ b/packages/commonwealth/client/scripts/helpers/getUrl.ts @@ -0,0 +1,48 @@ +import app from 'state'; + +export enum ServiceUrls { + chainEvents = 'chain-events', +} + +export function getBaseUrl(service?: ServiceUrls) { + if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') { + switch (service) { + case ServiceUrls.chainEvents: + return 'http://localhost:8081/api'; + default: + return 'http://localhost:8080' + app.serverUrl(); + } + } else { + switch (service) { + case ServiceUrls.chainEvents: + return 'https://chain-events.herokuapp.com/api'; + default: + // e.g. https://commonwealth.im/api + return window.location.origin + app.serverUrl(); + } + } +} + +export async function getFetch( + url: string, + queryParams?: { [key: string]: any } +) { + let queryUrl; + if (queryParams) queryUrl = url + '?' + new URLSearchParams(queryParams); + try { + const response = await fetch(queryUrl || url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (response.ok) { + const { result } = await response.json(); + return result; + } else { + console.error(`Fetch to ${queryUrl} failed, `, response); + } + } catch (e) { + console.error(e); + } +} diff --git a/packages/commonwealth/client/scripts/models/ChainEvent.ts b/packages/commonwealth/client/scripts/models/ChainEvent.ts index 9d21e1b942d..19eb7efe118 100644 --- a/packages/commonwealth/client/scripts/models/ChainEvent.ts +++ b/packages/commonwealth/client/scripts/models/ChainEvent.ts @@ -1,32 +1,30 @@ +import type { IChainEventData } from 'chain-events/src'; import _ from 'underscore'; -import type { IChainEventData, SupportedNetwork } from 'chain-events/src'; +import ChainEventType from './ChainEventType'; class ChainEvent { public readonly id?: number; public readonly blockNumber: number; public readonly data: IChainEventData; - public readonly chain: string; - public readonly network: SupportedNetwork; + public readonly type: ChainEventType; public eq(e: ChainEvent) { return e.data.kind === this.data.kind && _.isEqual(this.data, e.data); } - constructor(blockNumber, data, id?, chain?, network?) { + constructor(blockNumber, data, type, id?) { this.id = id; this.blockNumber = blockNumber; this.data = data; - this.chain = chain; - this.network = network; + this.type = type; } - public static fromJSON(json) { + public static fromJSON(json, chainEventType?: ChainEventType) { return new ChainEvent( json.blockNumber || json.block_number, json.data || json.event_data, - json.id, - json.chain, - json.network + chainEventType || ChainEventType.fromJSON(json.ChainEventType), + json.id ); } } diff --git a/packages/commonwealth/client/scripts/models/ChainEventType.ts b/packages/commonwealth/client/scripts/models/ChainEventType.ts index 58bb68cc5ca..21a29bbf5a2 100644 --- a/packages/commonwealth/client/scripts/models/ChainEventType.ts +++ b/packages/commonwealth/client/scripts/models/ChainEventType.ts @@ -22,3 +22,5 @@ class ChainEventType { ); } } + +export default ChainEventType; diff --git a/packages/commonwealth/client/scripts/models/DashboardActivityNotification.ts b/packages/commonwealth/client/scripts/models/DashboardActivityNotification.ts index f697d178838..65a272f6535 100644 --- a/packages/commonwealth/client/scripts/models/DashboardActivityNotification.ts +++ b/packages/commonwealth/client/scripts/models/DashboardActivityNotification.ts @@ -1,4 +1,5 @@ import type { IChainEventData, SupportedNetwork } from 'chain-events/src'; +import type { ChainEventType } from 'models'; import moment from 'moment'; class DashboardActivityNotification { @@ -17,6 +18,7 @@ class DashboardActivityNotification { } // Chain Event Notifications + public readonly typeId?: ChainEventType; public readonly blockNumber?: number; public readonly eventData?: IChainEventData; public readonly chainEventId?: number; @@ -34,6 +36,7 @@ class DashboardActivityNotification { likeCount, commentCount, isRead, + typeId, blockNumber, eventData, id, @@ -50,6 +53,7 @@ class DashboardActivityNotification { likeCount?: number; commentCount?: number; isRead?: boolean; + typeId?: ChainEventType; blockNumber?: number; eventData?: IChainEventData; id?: number; @@ -66,6 +70,7 @@ class DashboardActivityNotification { this.likeCount = likeCount; this.commentCount = commentCount; this._isRead = isRead; + this.typeId = typeId; this.blockNumber = blockNumber; this.eventData = eventData; this.chainEventId = id; @@ -85,11 +90,12 @@ class DashboardActivityNotification { likeCount: json.like_count, commentCount: json.comment_count, isRead: json.is_read, + typeId: json.chain_event_type_id, blockNumber: json.block_number, eventData: json.event_data, id: json.id, updatedAt: json.updated_at, - eventNetwork: json.network || json.event_network, + eventNetwork: json.event_network, chain: json.chain, iconUrl: json.icon_url, }); diff --git a/packages/commonwealth/client/scripts/models/Notification.ts b/packages/commonwealth/client/scripts/models/Notification.ts index 6d51262f449..38963c5ab25 100644 --- a/packages/commonwealth/client/scripts/models/Notification.ts +++ b/packages/commonwealth/client/scripts/models/Notification.ts @@ -1,3 +1,4 @@ +import type { ChainEventType } from 'models/index'; import moment from 'moment'; import ChainEvent from './ChainEvent'; import type NotificationSubscription from './NotificationSubscription'; @@ -31,14 +32,20 @@ class Notification { } } - public static fromJSON(json, subscription: NotificationSubscription) { + public static fromJSON( + json, + subscription: NotificationSubscription, + chainEventType?: ChainEventType + ) { return new Notification( json.id, json.notification_data, json.is_read, json.created_at, subscription, - json?.ChainEvent ? ChainEvent.fromJSON(json.ChainEvent) : undefined + json?.ChainEvent + ? ChainEvent.fromJSON(json.ChainEvent, chainEventType) + : undefined ); } } diff --git a/packages/commonwealth/client/scripts/models/NotificationSubscription.ts b/packages/commonwealth/client/scripts/models/NotificationSubscription.ts index 60f35e21379..54429e8711e 100644 --- a/packages/commonwealth/client/scripts/models/NotificationSubscription.ts +++ b/packages/commonwealth/client/scripts/models/NotificationSubscription.ts @@ -15,7 +15,7 @@ class NotificationSubscription { public readonly Thread: ThreadT; public readonly id?: number; - public readonly chainEntityId?: any; + public readonly chainEventTypeId?: any; private _immediateEmail: boolean; public get immediateEmail() { @@ -35,11 +35,6 @@ class NotificationSubscription { return this._isActive; } - // TODO: should resolve Chain vs chain - public get getChain() { - return this.Chain.id || this.Chain; - } - public enable() { this._isActive = true; } @@ -56,6 +51,7 @@ class NotificationSubscription { createdAt, immediateEmail, Chain?, + ChainEventTypeId?, comment?: CommentT, thread?: ThreadT ) { @@ -66,6 +62,7 @@ class NotificationSubscription { this.createdAt = moment(createdAt); this._immediateEmail = immediateEmail; this.Chain = Chain; + this.chainEventTypeId = ChainEventTypeId; this.Comment = comment; this.Thread = thread; } @@ -79,6 +76,7 @@ class NotificationSubscription { json.created_at, json.immediate_email, json.chain_id, + json.chain_event_type_id, json.Comment || json.offchain_comment_id, json.Thread || json.offchain_thread_id ); @@ -94,6 +92,7 @@ export const modelFromServer = (subscription: SubscriptionInstance) => { created_at, immediate_email, Chain, + chain_event_type_id, Comment, Thread, } = subscription; @@ -126,6 +125,7 @@ export const modelFromServer = (subscription: SubscriptionInstance) => { created_at, immediate_email, Chain, + chain_event_type_id, modeledComment, modeledThread ); diff --git a/packages/commonwealth/client/scripts/models/index.ts b/packages/commonwealth/client/scripts/models/index.ts index 0c4665e80d1..6890b48d1c3 100644 --- a/packages/commonwealth/client/scripts/models/index.ts +++ b/packages/commonwealth/client/scripts/models/index.ts @@ -31,6 +31,7 @@ export { default as SearchQuery } from './SearchQuery'; export { default as SearchResult } from './SearchResult'; export { default as SocialAccount } from './SocialAccount'; export { default as StorageModule } from './StorageModule'; +export { default as ChainEventType } from './ChainEventType'; export { default as ChainEvent } from './ChainEvent'; export { default as ChainEntity } from './ChainEntity'; export { default as StarredCommunity } from './StarredCommunity'; diff --git a/packages/commonwealth/client/scripts/views/components/notification_row.ts b/packages/commonwealth/client/scripts/views/components/notification_row.ts index bbb0e186d62..a4668530caf 100644 --- a/packages/commonwealth/client/scripts/views/components/notification_row.ts +++ b/packages/commonwealth/client/scripts/views/components/notification_row.ts @@ -326,12 +326,12 @@ const NotificationRow: m.Component< if (!notification.chainEvent) { throw new Error('chain event notification does not have expected data'); } - const chainId = notification.chainEvent.chain; + const chainId = notification.chainEvent.type.chain; // construct compatible CW event from DB by inserting network from type const chainEvent: CWEvent = { blockNumber: notification.chainEvent.blockNumber, - network: notification.chainEvent.network, + network: notification.chainEvent.type.eventNetwork, data: notification.chainEvent.data, }; const chainName = app.config.chains.getById(chainId)?.name; diff --git a/packages/commonwealth/client/scripts/views/pages/notification_settings/index.tsx b/packages/commonwealth/client/scripts/views/pages/notification_settings/index.tsx index cb41f60c66a..7dce56e56e6 100644 --- a/packages/commonwealth/client/scripts/views/pages/notification_settings/index.tsx +++ b/packages/commonwealth/client/scripts/views/pages/notification_settings/index.tsx @@ -28,7 +28,6 @@ import { CWPopoverMenu } from '../../components/component_kit/cw_popover/cw_popo import { CWTextInput } from '../../components/component_kit/cw_text_input'; import { CWCard } from '../../components/component_kit/cw_card'; import { CWSpinner } from '../../components/component_kit/cw_spinner'; -import { NotificationCategories } from '../../../../../../common-common/src/types'; const emailIntervalFrequencyMap = { never: 'Never', @@ -54,29 +53,7 @@ class NotificationSettingsPage extends ClassComponent { return ; } - // bundled discussion subscriptions - const bundledSubs = bundleSubs( - app.user.notifications.subscriptions.filter( - (x) => x.category !== 'chain-event' - ) - ); - // bundled chain-event subscriptions - const chainEventSubs = bundleSubs( - app.user.notifications.subscriptions.filter( - (x) => x.category === 'chain-event' - ) - ); - - const subscribedChainIds = - app.user.notifications.chainEventSubscribedChainIds; - - // chains/communities the user has addresses for but does not have existing subscriptions for - const relevantSubscribedChains = app.user.addresses - .map((x) => x.chain) - .filter( - (x) => subscribedChainIds.includes(x.id) && !chainEventSubs[x.id] - ); - + const bundledSubs = bundleSubs(app.user.notifications.subscriptions); const currentFrequency = app.user.emailInterval; return ( @@ -91,133 +68,6 @@ class NotificationSettingsPage extends ClassComponent { Notification settings for all new threads, comments, mentions, likes, and chain events in the following communities. - - Chain Events - -
- - Community - - - Email - - - In-App - -
- {relevantSubscribedChains.map((chain) => { - return ( -
-
-
-
- - - {chain.name} - -
-
- { - app.user.notifications - .enableImmediateEmails([]) - .then(() => { - m.redraw(); - }); - }} - /> - { - app.user.notifications - .subscribe(NotificationCategories.ChainEvent, chain.id) - .then(() => { - m.redraw(); - }); - }} - /> -
-
- ); - })} - {Object.entries(chainEventSubs).map(([chainName, subs]) => { - const chainInfo = app.config.chains.getById(chainName); - const hasSomeEmailSubs = subs.some((s) => s.immediateEmail); - const hasSomeInAppSubs = subs.some((s) => s.isActive); - return ( -
-
-
-
- - - {chainInfo?.name} - -
-
- { - hasSomeEmailSubs - ? app.user.notifications - .disableImmediateEmails(subs) - .then(() => { - m.redraw(); - }) - : app.user.notifications - .enableImmediateEmails(subs) - .then(() => { - m.redraw(); - }); - }} - /> - s.isActive)} - onchange={() => { - hasSomeInAppSubs - ? app.user.notifications - .disableSubscriptions(subs) - .then(() => { - m.redraw(); - }) - : app.user.notifications - .enableSubscriptions(subs) - .then(() => { - m.redraw(); - }); - }} - /> -
-
- ); - })} - - Discussion -