From 87d9497be9705b29a7e402ae61c1db3fc07b5a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Wed, 27 Sep 2023 14:23:05 +0100 Subject: [PATCH] refactor: prefer eventService.storeEvent methods (#4830) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://linear.app/unleash/issue/2-1403/consider-refactoring-the-way-tags-are-fetched-for-the-events This adds 2 methods to `EventService`: - `storeEvent`; - `storeEvents`; This allows us to run event-specific logic inside these methods. In the case of this PR, this means fetching the feature tags in case the event contains a `featureName` and there are no tags specified in the event. This prevents us from having to remember to fetch the tags in order to store feature-related events except for very specific cases, like the deletion of a feature - You can't fetch tags for a feature that no longer exists, so in that case we need to pre-fetch the tags before deleting the feature. This also allows us to do any event-specific post-processing to the event before reaching the DB layer. In general I think it's also nicer that we reference the event service instead of the event store directly. There's a lot of changes and a lot of files touched, but most of it is boilerplate to inject the `eventService` where needed instead of using the `eventStore` directly. Hopefully this will be a better approach than https://github.com/Unleash/unleash/pull/4729 --------- Co-authored-by: Gastón Fournier --- .../features/access/createAccessService.ts | 20 ++- .../createExportImportService.ts | 41 +++-- .../export-import-service.ts | 15 +- .../createFeatureToggleService.ts | 31 ++-- src/lib/features/group/createGroupService.ts | 15 +- .../features/project/createProjectService.ts | 28 +++- .../features/segment/createSegmentService.ts | 26 ++- .../middleware/cors-origin-middleware.test.ts | 12 +- src/lib/routes/admin-api/events.test.ts | 26 +-- src/lib/services/access-service.test.ts | 9 +- src/lib/services/account-service.ts | 2 +- src/lib/services/addon-service.test.ts | 78 +++++---- src/lib/services/addon-service.ts | 23 ++- src/lib/services/api-token-service.test.ts | 42 ++++- src/lib/services/api-token-service.ts | 20 ++- src/lib/services/context-service.ts | 19 +-- src/lib/services/event-service.ts | 63 +++++++- src/lib/services/favorites-service.ts | 25 ++- .../feature-service-potentially-stale.test.ts | 27 +++- src/lib/services/feature-tag-service.ts | 25 +-- src/lib/services/feature-toggle-service.ts | 150 ++++++------------ src/lib/services/group-service.ts | 18 +-- src/lib/services/index.ts | 39 +++-- src/lib/services/pat-service.ts | 16 +- src/lib/services/project-service.ts | 35 ++-- .../services/public-signup-token-service.ts | 21 ++- src/lib/services/segment-service.ts | 23 ++- src/lib/services/setting-service.ts | 18 +-- src/lib/services/state-service.test.ts | 34 ++-- src/lib/services/state-service.ts | 31 ++-- src/lib/services/strategy-service.ts | 22 ++- src/lib/services/tag-service.ts | 16 +- src/lib/services/tag-type-service.ts | 18 +-- src/lib/services/user-service.test.ts | 93 ++++++++--- src/lib/services/user-service.ts | 15 +- src/lib/types/events.ts | 64 +++----- src/test/e2e/api/admin/event.e2e.test.ts | 14 +- .../reset-password-controller.e2e.test.ts | 8 +- .../auth/simple-password-provider.e2e.test.ts | 7 +- .../e2e/services/access-service.e2e.test.ts | 11 +- .../e2e/services/addon-service.e2e.test.ts | 5 +- .../services/api-token-service.e2e.test.ts | 12 +- .../feature-toggle-service-v2.e2e.test.ts | 15 +- .../e2e/services/group-service.e2e.test.ts | 5 +- .../e2e/services/playground-service.test.ts | 6 +- .../project-health-service.e2e.test.ts | 11 +- .../e2e/services/project-service.e2e.test.ts | 38 ++--- .../services/reset-token-service.e2e.test.ts | 8 +- src/test/e2e/services/setting-service.test.ts | 4 +- .../e2e/services/state-service.e2e.test.ts | 4 +- .../e2e/services/user-service.e2e.test.ts | 7 +- src/test/e2e/stores/event-store.e2e.test.ts | 1 - 52 files changed, 777 insertions(+), 539 deletions(-) diff --git a/src/lib/features/access/createAccessService.ts b/src/lib/features/access/createAccessService.ts index 0dfd615873..e57296e78d 100644 --- a/src/lib/features/access/createAccessService.ts +++ b/src/lib/features/access/createAccessService.ts @@ -5,13 +5,15 @@ import { AccountStore } from '../../db/account-store'; import RoleStore from '../../db/role-store'; import EnvironmentStore from '../../db/environment-store'; import { AccessStore } from '../../db/access-store'; -import { AccessService, GroupService } from '../../services'; +import { AccessService, EventService, GroupService } from '../../services'; import FakeGroupStore from '../../../test/fixtures/fake-group-store'; import FakeEventStore from '../../../test/fixtures/fake-event-store'; import { FakeAccountStore } from '../../../test/fixtures/fake-account-store'; import FakeRoleStore from '../../../test/fixtures/fake-role-store'; import FakeEnvironmentStore from '../../../test/fixtures/fake-environment-store'; import FakeAccessStore from '../../../test/fixtures/fake-access-store'; +import FeatureTagStore from '../../db/feature-tag-store'; +import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store'; export const createAccessService = ( db: Db, @@ -24,9 +26,15 @@ export const createAccessService = ( const roleStore = new RoleStore(db, eventBus, getLogger); const environmentStore = new EnvironmentStore(db, eventBus, getLogger); const accessStore = new AccessStore(db, eventBus, getLogger); + const featureTagStore = new FeatureTagStore(db, eventBus, getLogger); + const eventService = new EventService( + { eventStore, featureTagStore }, + config, + ); const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + { groupStore, accountStore }, { getLogger }, + eventService, ); return new AccessService( @@ -46,9 +54,15 @@ export const createFakeAccessService = ( const roleStore = new FakeRoleStore(); const environmentStore = new FakeEnvironmentStore(); const accessStore = new FakeAccessStore(roleStore); + const featureTagStore = new FakeFeatureTagStore(); + const eventService = new EventService( + { eventStore, featureTagStore }, + config, + ); const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + { groupStore, accountStore }, { getLogger }, + eventService, ); return new AccessService( diff --git a/src/lib/features/export-import-toggles/createExportImportService.ts b/src/lib/features/export-import-toggles/createExportImportService.ts index c06b619308..0b0b1b7d5b 100644 --- a/src/lib/features/export-import-toggles/createExportImportService.ts +++ b/src/lib/features/export-import-toggles/createExportImportService.ts @@ -12,6 +12,7 @@ import ContextFieldStore from '../../db/context-field-store'; import FeatureStrategiesStore from '../../db/feature-strategy-store'; import { ContextService, + EventService, FeatureTagService, StrategyService, TagTypeService, @@ -63,36 +64,45 @@ export const createFakeExportImportTogglesService = ( const featureToggleService = createFakeFeatureToggleService(config); const privateProjectChecker = createFakePrivateProjectChecker(); + const eventService = new EventService( + { + eventStore, + featureTagStore, + }, + config, + ); + const featureTagService = new FeatureTagService( { tagStore, featureTagStore, - eventStore, featureToggleStore, }, { getLogger }, + eventService, ); const contextService = new ContextService( { projectStore, - eventStore, contextFieldStore, featureStrategiesStore, }, { getLogger, flagResolver }, + eventService, privateProjectChecker, ); const strategyService = new StrategyService( - { strategyStore, eventStore }, + { strategyStore }, { getLogger }, + eventService, ); const tagTypeService = new TagTypeService( - { tagTypeStore, eventStore }, + { tagTypeStore }, { getLogger }, + eventService, ); const exportImportService = new ExportImportService( { - eventStore, importTogglesStore, featureStrategiesStore, contextFieldStore, @@ -107,6 +117,7 @@ export const createFakeExportImportTogglesService = ( featureToggleService, featureTagService, accessService, + eventService, contextService, strategyService, tagTypeService, @@ -160,36 +171,45 @@ export const createExportImportTogglesService = ( const featureToggleService = createFeatureToggleService(db, config); const privateProjectChecker = createPrivateProjectChecker(db, config); + const eventService = new EventService( + { + eventStore, + featureTagStore, + }, + config, + ); + const featureTagService = new FeatureTagService( { tagStore, featureTagStore, - eventStore, featureToggleStore, }, { getLogger }, + eventService, ); const contextService = new ContextService( { projectStore, - eventStore, contextFieldStore, featureStrategiesStore, }, { getLogger, flagResolver }, + eventService, privateProjectChecker, ); const strategyService = new StrategyService( - { strategyStore, eventStore }, + { strategyStore }, { getLogger }, + eventService, ); const tagTypeService = new TagTypeService( - { tagTypeStore, eventStore }, + { tagTypeStore }, { getLogger }, + eventService, ); const exportImportService = new ExportImportService( { - eventStore, importTogglesStore, featureStrategiesStore, contextFieldStore, @@ -204,6 +224,7 @@ export const createExportImportTogglesService = ( featureToggleService, featureTagService, accessService, + eventService, contextService, strategyService, tagTypeService, diff --git a/src/lib/features/export-import-toggles/export-import-service.ts b/src/lib/features/export-import-toggles/export-import-service.ts index f05db80a29..a480171756 100644 --- a/src/lib/features/export-import-toggles/export-import-service.ts +++ b/src/lib/features/export-import-toggles/export-import-service.ts @@ -8,7 +8,6 @@ import { import { Logger } from '../../logger'; import { IFeatureTagStore } from '../../types/stores/feature-tag-store'; import { ITagTypeStore } from '../../types/stores/tag-type-store'; -import { IEventStore } from '../../types/stores/event-store'; import { IStrategy } from '../../types/stores/strategy-store'; import { IFeatureToggleStore } from '../../types/stores/feature-toggle-store'; import { IFeatureStrategiesStore } from '../../types/stores/feature-strategies-store'; @@ -35,6 +34,7 @@ import { extractUsernameFromUser } from '../../util'; import { AccessService, ContextService, + EventService, FeatureTagService, FeatureToggleService, StrategyService, @@ -57,8 +57,6 @@ export default class ExportImportService { private featureStrategiesStore: IFeatureStrategiesStore; - private eventStore: IEventStore; - private importTogglesStore: IImportTogglesStore; private tagTypeStore: ITagTypeStore; @@ -81,6 +79,8 @@ export default class ExportImportService { private accessService: AccessService; + private eventService: EventService; + private tagTypeService: TagTypeService; private featureTagService: FeatureTagService; @@ -91,7 +91,6 @@ export default class ExportImportService { stores: Pick< IUnleashStores, | 'importTogglesStore' - | 'eventStore' | 'featureStrategiesStore' | 'featureToggleStore' | 'featureEnvironmentStore' @@ -109,6 +108,7 @@ export default class ExportImportService { strategyService, contextService, accessService, + eventService, tagTypeService, featureTagService, }: Pick< @@ -117,11 +117,11 @@ export default class ExportImportService { | 'strategyService' | 'contextService' | 'accessService' + | 'eventService' | 'tagTypeService' | 'featureTagService' >, ) { - this.eventStore = stores.eventStore; this.toggleStore = stores.featureToggleStore; this.importTogglesStore = stores.importTogglesStore; this.featureStrategiesStore = stores.featureStrategiesStore; @@ -135,6 +135,7 @@ export default class ExportImportService { this.strategyService = strategyService; this.contextService = contextService; this.accessService = accessService; + this.eventService = eventService; this.tagTypeService = tagTypeService; this.featureTagService = featureTagService; this.importPermissionsService = new ImportPermissionsService( @@ -237,7 +238,7 @@ export default class ExportImportService { await this.importToggleLevelInfo(cleanedDto, user); await this.importDefault(cleanedDto, user); - await this.eventStore.store({ + await this.eventService.storeEvent({ project: cleanedDto.project, environment: cleanedDto.environment, type: FEATURES_IMPORTED, @@ -726,7 +727,7 @@ export default class ExportImportService { }), tagTypes: filteredTagTypes, }; - await this.eventStore.store({ + await this.eventService.storeEvent({ type: FEATURES_EXPORTED, createdBy: userName, data: result, diff --git a/src/lib/features/feature-toggle/createFeatureToggleService.ts b/src/lib/features/feature-toggle/createFeatureToggleService.ts index 8ba3e96119..50ce9098dd 100644 --- a/src/lib/features/feature-toggle/createFeatureToggleService.ts +++ b/src/lib/features/feature-toggle/createFeatureToggleService.ts @@ -1,5 +1,6 @@ import { AccessService, + EventService, FeatureToggleService, GroupService, } from '../../services'; @@ -7,7 +8,6 @@ import FeatureStrategiesStore from '../../db/feature-strategy-store'; import FeatureToggleStore from '../../db/feature-toggle-store'; import FeatureToggleClientStore from '../../db/feature-toggle-client-store'; import ProjectStore from '../../db/project-store'; -import FeatureTagStore from '../../db/feature-tag-store'; import { FeatureEnvironmentStore } from '../../db/feature-environment-store'; import ContextFieldStore from '../../db/context-field-store'; import GroupStore from '../../db/group-store'; @@ -22,7 +22,6 @@ import FakeFeatureStrategiesStore from '../../../test/fixtures/fake-feature-stra import FakeFeatureToggleStore from '../../../test/fixtures/fake-feature-toggle-store'; import FakeFeatureToggleClientStore from '../../../test/fixtures/fake-feature-toggle-client-store'; import FakeProjectStore from '../../../test/fixtures/fake-project-store'; -import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store'; import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store'; import FakeContextFieldStore from '../../../test/fixtures/fake-context-field-store'; import FakeGroupStore from '../../../test/fixtures/fake-group-store'; @@ -47,6 +46,8 @@ import { } from '../private-project/createPrivateProjectChecker'; import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model'; import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model'; +import FeatureTagStore from '../../db/feature-tag-store'; +import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store'; export const createFeatureToggleService = ( db: Db, @@ -72,7 +73,6 @@ export const createFeatureToggleService = ( getLogger, flagResolver, ); - const featureTagStore = new FeatureTagStore(db, eventBus, getLogger); const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, @@ -87,13 +87,19 @@ export const createFeatureToggleService = ( const strategyStore = new StrategyStore(db, getLogger); const accountStore = new AccountStore(db, getLogger); const accessStore = new AccessStore(db, eventBus, getLogger); + const featureTagStore = new FeatureTagStore(db, eventBus, getLogger); const roleStore = new RoleStore(db, eventBus, getLogger); const environmentStore = new EnvironmentStore(db, eventBus, getLogger); const eventStore = new EventStore(db, getLogger); - const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + const eventService = new EventService( + { eventStore, featureTagStore }, { getLogger }, ); + const groupService = new GroupService( + { groupStore, accountStore }, + { getLogger }, + eventService, + ); const accessService = new AccessService( { accessStore, accountStore, roleStore, environmentStore, groupStore }, { getLogger, flagResolver }, @@ -115,7 +121,6 @@ export const createFeatureToggleService = ( featureToggleStore, featureToggleClientStore, projectStore, - eventStore, featureTagStore, featureEnvironmentStore, contextFieldStore, @@ -124,6 +129,7 @@ export const createFeatureToggleService = ( { getLogger, flagResolver }, segmentService, accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, @@ -141,18 +147,23 @@ export const createFakeFeatureToggleService = ( const featureToggleStore = new FakeFeatureToggleStore(); const featureToggleClientStore = new FakeFeatureToggleClientStore(); const projectStore = new FakeProjectStore(); - const featureTagStore = new FakeFeatureTagStore(); const featureEnvironmentStore = new FakeFeatureEnvironmentStore(); const contextFieldStore = new FakeContextFieldStore(); const groupStore = new FakeGroupStore(); const accountStore = new FakeAccountStore(); const accessStore = new FakeAccessStore(); + const featureTagStore = new FakeFeatureTagStore(); const roleStore = new FakeRoleStore(); const environmentStore = new FakeEnvironmentStore(); - const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + const eventService = new EventService( + { eventStore, featureTagStore }, { getLogger }, ); + const groupService = new GroupService( + { groupStore, accountStore }, + { getLogger }, + eventService, + ); const accessService = new AccessService( { accessStore, accountStore, roleStore, environmentStore, groupStore }, { getLogger, flagResolver }, @@ -168,7 +179,6 @@ export const createFakeFeatureToggleService = ( featureToggleStore, featureToggleClientStore, projectStore, - eventStore, featureTagStore, featureEnvironmentStore, contextFieldStore, @@ -177,6 +187,7 @@ export const createFakeFeatureToggleService = ( { getLogger, flagResolver }, segmentService, accessService, + eventService, changeRequestAccessReadModel, fakePrivateProjectChecker, dependentFeaturesReadModel, diff --git a/src/lib/features/group/createGroupService.ts b/src/lib/features/group/createGroupService.ts index 8e5a8128ce..05f19880a0 100644 --- a/src/lib/features/group/createGroupService.ts +++ b/src/lib/features/group/createGroupService.ts @@ -1,21 +1,28 @@ import { IUnleashConfig } from '../../types'; -import { GroupService } from '../../services'; +import { EventService, GroupService } from '../../services'; import { Db } from '../../db/db'; import GroupStore from '../../db/group-store'; import { AccountStore } from '../../db/account-store'; import EventStore from '../../db/event-store'; +import FeatureTagStore from '../../db/feature-tag-store'; export const createGroupService = ( db: Db, config: IUnleashConfig, ): GroupService => { - const { getLogger } = config; + const { getLogger, eventBus } = config; const groupStore = new GroupStore(db); const accountStore = new AccountStore(db, getLogger); const eventStore = new EventStore(db, getLogger); - const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + const featureTagStore = new FeatureTagStore(db, eventBus, getLogger); + const eventService = new EventService( + { eventStore, featureTagStore }, { getLogger }, ); + const groupService = new GroupService( + { groupStore, accountStore }, + { getLogger }, + eventService, + ); return groupService; }; diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts index 2121e4530a..58d2830ae7 100644 --- a/src/lib/features/project/createProjectService.ts +++ b/src/lib/features/project/createProjectService.ts @@ -5,6 +5,7 @@ import { AccountStore } from '../../db/account-store'; import EnvironmentStore from '../../db/environment-store'; import { AccessService, + EventService, FavoritesService, GroupService, ProjectService, @@ -39,6 +40,7 @@ import { createFakePrivateProjectChecker, createPrivateProjectChecker, } from '../private-project/createPrivateProjectChecker'; +import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store'; export const createProjectService = ( db: Db, @@ -75,17 +77,25 @@ export const createProjectService = ( eventBus, getLogger, ); + const eventService = new EventService( + { + eventStore, + featureTagStore: new FakeFeatureTagStore(), + }, + config, + ); const favoriteService = new FavoritesService( { favoriteFeaturesStore, favoriteProjectsStore, - eventStore, }, config, + eventService, ); const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + { groupStore, accountStore }, { getLogger }, + eventService, ); const privateProjectChecker = createPrivateProjectChecker(db, config); @@ -106,6 +116,7 @@ export const createProjectService = ( featureToggleService, groupService, favoriteService, + eventService, privateProjectChecker, ); }; @@ -127,17 +138,25 @@ export const createFakeProjectService = ( const featureToggleService = createFakeFeatureToggleService(config); const favoriteFeaturesStore = new FakeFavoriteFeaturesStore(); const favoriteProjectsStore = new FakeFavoriteProjectsStore(); + const eventService = new EventService( + { + eventStore, + featureTagStore: new FakeFeatureTagStore(), + }, + config, + ); const favoriteService = new FavoritesService( { favoriteFeaturesStore, favoriteProjectsStore, - eventStore, }, config, + eventService, ); const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + { groupStore, accountStore }, { getLogger }, + eventService, ); const privateProjectChecker = createFakePrivateProjectChecker(); @@ -158,6 +177,7 @@ export const createFakeProjectService = ( featureToggleService, groupService, favoriteService, + eventService, privateProjectChecker, ); }; diff --git a/src/lib/features/segment/createSegmentService.ts b/src/lib/features/segment/createSegmentService.ts index 5306d3f1b5..f558437084 100644 --- a/src/lib/features/segment/createSegmentService.ts +++ b/src/lib/features/segment/createSegmentService.ts @@ -1,6 +1,6 @@ import { Db, IUnleashConfig } from 'lib/server-impl'; import EventStore from '../../db/event-store'; -import { SegmentService } from '../../services'; +import { EventService, SegmentService } from '../../services'; import FakeEventStore from '../../../test/fixtures/fake-event-store'; import { ISegmentService } from '../../segments/segment-service-interface'; import FeatureStrategiesStore from '../../db/feature-strategy-store'; @@ -15,6 +15,8 @@ import { createFakePrivateProjectChecker, createPrivateProjectChecker, } from '../private-project/createPrivateProjectChecker'; +import FeatureTagStore from '../../db/feature-tag-store'; +import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store'; export const createSegmentService = ( db: Db, @@ -40,10 +42,19 @@ export const createSegmentService = ( ); const privateProjectChecker = createPrivateProjectChecker(db, config); + const eventService = new EventService( + { + eventStore, + featureTagStore: new FeatureTagStore(db, eventBus, getLogger), + }, + config, + ); + return new SegmentService( - { segmentStore, featureStrategiesStore, eventStore }, + { segmentStore, featureStrategiesStore }, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ); }; @@ -58,10 +69,19 @@ export const createFakeSegmentService = ( const privateProjectChecker = createFakePrivateProjectChecker(); + const eventService = new EventService( + { + eventStore, + featureTagStore: new FakeFeatureTagStore(), + }, + config, + ); + return new SegmentService( - { segmentStore, featureStrategiesStore, eventStore }, + { segmentStore, featureStrategiesStore }, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ); }; diff --git a/src/lib/middleware/cors-origin-middleware.test.ts b/src/lib/middleware/cors-origin-middleware.test.ts index 95206b4f87..bc760eeabf 100644 --- a/src/lib/middleware/cors-origin-middleware.test.ts +++ b/src/lib/middleware/cors-origin-middleware.test.ts @@ -4,10 +4,11 @@ import { createTestConfig } from '../../test/config/test-config'; import FakeEventStore from '../../test/fixtures/fake-event-store'; import { randomId } from '../util/random-id'; import FakeProjectStore from '../../test/fixtures/fake-project-store'; -import { ProxyService, SettingService } from '../../lib/services'; +import { EventService, ProxyService, SettingService } from '../../lib/services'; import { ISettingStore } from '../../lib/types'; import { frontendSettingsKey } from '../../lib/types/settings/frontend-settings'; import { minutesToMilliseconds } from 'date-fns'; +import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store'; const createSettingService = ( frontendApiOrigins: string[], @@ -17,11 +18,14 @@ const createSettingService = ( const stores = { settingStore: new FakeSettingStore(), eventStore: new FakeEventStore(), + featureTagStore: new FakeFeatureTagStore(), projectStore: new FakeProjectStore(), }; + const eventService = new EventService(stores, config); + const services = { - settingService: new SettingService(stores, config), + settingService: new SettingService(stores, config, eventService), }; return { @@ -135,8 +139,8 @@ test('corsOriginMiddleware with caching enabled', async () => { /* This is needed because it is not enough to fake time to test the - updated cache, we also need to make sure that all promises are - executed and completed, in the right order. + updated cache, we also need to make sure that all promises are + executed and completed, in the right order. */ await new Promise((resolve) => process.nextTick(async () => { diff --git a/src/lib/routes/admin-api/events.test.ts b/src/lib/routes/admin-api/events.test.ts index 7e999e6240..db37ba9321 100644 --- a/src/lib/routes/admin-api/events.test.ts +++ b/src/lib/routes/admin-api/events.test.ts @@ -22,7 +22,11 @@ async function getSetup(anonymise: boolean = false) { const services = createServices(stores, config); const app = await getApp(config, stores, services); - return { base, eventStore: stores.eventStore, request: supertest(app) }; + return { + base, + eventService: services.eventService, + request: supertest(app), + }; } test('should get empty events list via admin', async () => { @@ -38,14 +42,13 @@ test('should get empty events list via admin', async () => { }); test('should get events list via admin', async () => { - const { request, base, eventStore } = await getSetup(); - eventStore.store( + const { request, base, eventService } = await getSetup(); + eventService.storeEvent( new FeatureCreatedEvent({ createdBy: 'some@email.com', data: { name: 'test', project: 'default' }, featureName: 'test', project: 'default', - tags: [], }), ); const { body } = await request @@ -58,14 +61,13 @@ test('should get events list via admin', async () => { }); test('should anonymise events list via admin', async () => { - const { request, base, eventStore } = await getSetup(true); - eventStore.store( + const { request, base, eventService } = await getSetup(true); + eventService.storeEvent( new FeatureCreatedEvent({ createdBy: 'some@email.com', data: { name: 'test', project: 'default' }, featureName: 'test', project: 'default', - tags: [], }), ); const { body } = await request @@ -81,15 +83,15 @@ test('should also anonymise email fields in data and preData properties', async const email1 = 'test1@email.com'; const email2 = 'test2@email.com'; - const { request, base, eventStore } = await getSetup(true); - eventStore.store( + const { request, base, eventService } = await getSetup(true); + eventService.storeEvent( new ProjectUserAddedEvent({ createdBy: 'some@email.com', data: { name: 'test', project: 'default', email: email1 }, project: 'default', }), ); - eventStore.store( + eventService.storeEvent( new ProjectUserRemovedEvent({ createdBy: 'some@email.com', preData: { name: 'test', project: 'default', email: email2 }, @@ -109,8 +111,8 @@ test('should also anonymise email fields in data and preData properties', async test('should anonymise any PII fields, no matter the depth', async () => { const testUsername = 'test-username'; - const { request, base, eventStore } = await getSetup(true); - eventStore.store( + const { request, base, eventService } = await getSetup(true); + eventService.storeEvent( new ProjectAccessAddedEvent({ createdBy: 'some@email.com', data: { diff --git a/src/lib/services/access-service.test.ts b/src/lib/services/access-service.test.ts index 688ce80279..211896ef94 100644 --- a/src/lib/services/access-service.test.ts +++ b/src/lib/services/access-service.test.ts @@ -13,6 +13,8 @@ import { GroupService } from '../services/group-service'; import FakeEventStore from '../../test/fixtures/fake-event-store'; import { IRole } from 'lib/types/stores/access-store'; import { IGroup } from 'lib/types'; +import EventService from './event-service'; +import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store'; function getSetup(customRootRolesKillSwitch: boolean = true) { const config = createTestConfig({ @@ -204,9 +206,14 @@ test('throws error when trying to delete a project role in use by group', async accessStore.get = async (): Promise => { return { id: 1, type: 'custom', name: 'project role' }; }; + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const groupService = new GroupService( - { groupStore, eventStore, accountStore }, + { groupStore, accountStore }, { getLogger }, + eventService, ); const accessService = new AccessService( diff --git a/src/lib/services/account-service.ts b/src/lib/services/account-service.ts index 9dc145c869..2f13b3403b 100644 --- a/src/lib/services/account-service.ts +++ b/src/lib/services/account-service.ts @@ -23,7 +23,7 @@ export class AccountService { private lastSeenSecrets: Set = new Set(); constructor( - stores: Pick, + stores: Pick, { getLogger }: Pick, services: { accessService: AccessService; diff --git a/src/lib/services/addon-service.test.ts b/src/lib/services/addon-service.test.ts index 590114e607..91be2a89f2 100644 --- a/src/lib/services/addon-service.test.ts +++ b/src/lib/services/addon-service.test.ts @@ -14,6 +14,7 @@ import AddonService from './addon-service'; import { IAddonDto } from '../types/stores/addon-store'; import SimpleAddon from './addon-service-test-simple-addon'; import { IAddonProviders } from '../addons'; +import EventService from './event-service'; const MASKED_VALUE = '*****'; @@ -21,7 +22,12 @@ let addonProvider: IAddonProviders; function getSetup() { const stores = createStores(); - const tagTypeService = new TagTypeService(stores, { getLogger }); + const eventService = new EventService(stores, { getLogger }); + const tagTypeService = new TagTypeService( + stores, + { getLogger }, + eventService, + ); addonProvider = { simple: new SimpleAddon() }; return { @@ -33,8 +39,10 @@ function getSetup() { server: { unleashUrl: 'http://test' }, }, tagTypeService, + eventService, addonProvider, ), + eventService, stores, tagTypeService, }; @@ -77,7 +85,7 @@ test('should not allow addon-config for unknown provider', async () => { }); test('should trigger simple-addon eventHandler', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const config = { provider: 'simple', @@ -93,7 +101,7 @@ test('should trigger simple-addon eventHandler', async () => { await addonService.createAddon(config, 'me@mail.com'); // Feature toggle was created - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', data: { @@ -113,7 +121,7 @@ test('should trigger simple-addon eventHandler', async () => { }); test('should not trigger event handler if project of event is different from addon', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const config = { provider: 'simple', enabled: true, @@ -126,7 +134,7 @@ test('should not trigger event handler if project of event is different from add }; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: 'someotherproject', @@ -144,7 +152,7 @@ test('should not trigger event handler if project of event is different from add }); test('should trigger event handler if project for event is one of the desired projects for addon', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const desiredProject = 'desired'; const otherProject = 'other'; const config = { @@ -159,7 +167,7 @@ test('should trigger event handler if project for event is one of the desired pr }; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProject, @@ -169,7 +177,7 @@ test('should trigger event handler if project for event is one of the desired pr strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: otherProject, @@ -189,7 +197,7 @@ test('should trigger event handler if project for event is one of the desired pr }); test('should trigger events for multiple projects if addon is setup to filter multiple projects', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const desiredProjects = ['desired', 'desired2']; const otherProject = 'other'; const config = { @@ -204,7 +212,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu }; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[0], @@ -214,7 +222,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: otherProject, @@ -224,7 +232,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[1], @@ -246,7 +254,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu }); test('should filter events on environment if addon is setup to filter for it', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const desiredEnvironment = 'desired'; const otherEnvironment = 'other'; const config = { @@ -262,7 +270,7 @@ test('should filter events on environment if addon is setup to filter for it', a }; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredEnvironment, @@ -273,7 +281,7 @@ test('should filter events on environment if addon is setup to filter for it', a strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', environment: otherEnvironment, @@ -293,7 +301,7 @@ test('should filter events on environment if addon is setup to filter for it', a }); test('should not filter out global events (no specific environment) even if addon is setup to filter for environments', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const filteredEnvironment = 'filtered'; const config = { provider: 'simple', @@ -319,7 +327,7 @@ test('should not filter out global events (no specific environment) even if addo }; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store(globalEventWithNoEnvironment); + await eventService.storeEvent(globalEventWithNoEnvironment); const simpleProvider = addonService.addonProviders.simple; // @ts-expect-error const events = simpleProvider.getEvents(); @@ -330,7 +338,7 @@ test('should not filter out global events (no specific environment) even if addo }); test('should not filter out global events (no specific project) even if addon is setup to filter for projects', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const filteredProject = 'filtered'; const config = { provider: 'simple', @@ -355,7 +363,7 @@ test('should not filter out global events (no specific project) even if addon is }; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store(globalEventWithNoProject); + await eventService.storeEvent(globalEventWithNoProject); const simpleProvider = addonService.addonProviders.simple; // @ts-expect-error const events = simpleProvider.getEvents(); @@ -366,7 +374,7 @@ test('should not filter out global events (no specific project) even if addon is }); test('should support wildcard option for filtering addons', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const desiredProjects = ['desired', 'desired2']; const otherProject = 'other'; const config = { @@ -381,7 +389,7 @@ test('should support wildcard option for filtering addons', async () => { }; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[0], @@ -391,7 +399,7 @@ test('should support wildcard option for filtering addons', async () => { strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: otherProject, @@ -401,7 +409,7 @@ test('should support wildcard option for filtering addons', async () => { strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[1], @@ -425,7 +433,7 @@ test('should support wildcard option for filtering addons', async () => { }); test('Should support filtering by both project and environment', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const desiredProjects = ['desired1', 'desired2', 'desired3']; const desiredEnvironments = ['env1', 'env2', 'env3']; const config = { @@ -445,7 +453,7 @@ test('Should support filtering by both project and environment', async () => { 'desired-toggle3', ]; await addonService.createAddon(config, 'me@mail.com'); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[0], @@ -456,7 +464,7 @@ test('Should support filtering by both project and environment', async () => { strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[0], @@ -467,7 +475,7 @@ test('Should support filtering by both project and environment', async () => { strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[2], @@ -478,7 +486,7 @@ test('Should support filtering by both project and environment', async () => { strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: desiredProjects[2], @@ -489,7 +497,7 @@ test('Should support filtering by both project and environment', async () => { strategies: [{ name: 'default' }], }, }); - await stores.eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, createdBy: 'some@user.com', project: 'wrongproject', @@ -556,7 +564,7 @@ test('should create tag type for simple-addon', async () => { }); test('should store ADDON_CONFIG_CREATE event', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const config = { provider: 'simple', @@ -571,7 +579,7 @@ test('should store ADDON_CONFIG_CREATE event', async () => { await addonService.createAddon(config, 'me@mail.com'); - const events = await stores.eventStore.getEvents(); + const { events } = await eventService.getEvents(); expect(events.length).toBe(2); // Also tag-types where created expect(events[1].type).toBe(ADDON_CONFIG_CREATED); @@ -579,7 +587,7 @@ test('should store ADDON_CONFIG_CREATE event', async () => { }); test('should store ADDON_CONFIG_UPDATE event', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const config: IAddonDto = { description: '', @@ -597,7 +605,7 @@ test('should store ADDON_CONFIG_UPDATE event', async () => { const updated = { ...addonConfig, description: 'test' }; await addonService.updateAddon(addonConfig.id, updated, 'me@mail.com'); - const events = await stores.eventStore.getEvents(); + const { events } = await eventService.getEvents(); expect(events.length).toBe(3); expect(events[2].type).toBe(ADDON_CONFIG_UPDATED); @@ -605,7 +613,7 @@ test('should store ADDON_CONFIG_UPDATE event', async () => { }); test('should store ADDON_CONFIG_REMOVE event', async () => { - const { addonService, stores } = getSetup(); + const { addonService, eventService } = getSetup(); const config: IAddonDto = { provider: 'simple', @@ -622,7 +630,7 @@ test('should store ADDON_CONFIG_REMOVE event', async () => { await addonService.removeAddon(addonConfig.id, 'me@mail.com'); - const events = await stores.eventStore.getEvents(); + const { events } = await eventService.getEvents(); expect(events.length).toBe(3); expect(events[2].type).toBe(ADDON_CONFIG_DELETED); diff --git a/src/lib/services/addon-service.ts b/src/lib/services/addon-service.ts index 3105d321a9..fc76fe696b 100644 --- a/src/lib/services/addon-service.ts +++ b/src/lib/services/addon-service.ts @@ -4,7 +4,6 @@ import { getAddons, IAddonProviders } from '../addons'; import * as events from '../types/events'; import { addonSchema } from './addon-schema'; import NameExistsError from '../error/name-exists-error'; -import { IEventStore } from '../types/stores/event-store'; import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; import { Logger } from '../logger'; import TagTypeService from './tag-type-service'; @@ -12,6 +11,7 @@ import { IAddon, IAddonDto, IAddonStore } from '../types/stores/addon-store'; import { IUnleashStores, IUnleashConfig } from '../types'; import { IAddonDefinition } from '../types/model'; import { minutesToMilliseconds } from 'date-fns'; +import EventService from './event-service'; const SUPPORTED_EVENTS = Object.keys(events).map((k) => events[k]); @@ -23,8 +23,6 @@ interface ISensitiveParams { [key: string]: string[]; } export default class AddonService { - eventStore: IEventStore; - addonStore: IAddonStore; featureToggleStore: IFeatureToggleStore; @@ -33,6 +31,8 @@ export default class AddonService { tagTypeService: TagTypeService; + eventService: EventService; + addonProviders: IAddonProviders; sensitiveParams: ISensitiveParams; @@ -43,25 +43,22 @@ export default class AddonService { constructor( { addonStore, - eventStore, featureToggleStore, - }: Pick< - IUnleashStores, - 'addonStore' | 'eventStore' | 'featureToggleStore' - >, + }: Pick, { getLogger, server, flagResolver, }: Pick, tagTypeService: TagTypeService, + eventService: EventService, addons?: IAddonProviders, ) { - this.eventStore = eventStore; this.addonStore = addonStore; this.featureToggleStore = featureToggleStore; this.logger = getLogger('services/addon-service.js'); this.tagTypeService = tagTypeService; + this.eventService = eventService; this.addonProviders = addons || @@ -102,7 +99,7 @@ export default class AddonService { registerEventHandler(): void { SUPPORTED_EVENTS.forEach((eventName) => - this.eventStore.on(eventName, this.handleEvent(eventName)), + this.eventService.onEvent(eventName, this.handleEvent(eventName)), ); } @@ -205,7 +202,7 @@ export default class AddonService { `User ${userName} created addon ${addonConfig.provider}`, ); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: events.ADDON_CONFIG_CREATED, createdBy: userName, data: { provider: addonConfig.provider }, @@ -238,7 +235,7 @@ export default class AddonService { ); } const result = await this.addonStore.update(id, addonConfig); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: events.ADDON_CONFIG_UPDATED, createdBy: userName, data: { id, provider: addonConfig.provider }, @@ -249,7 +246,7 @@ export default class AddonService { async removeAddon(id: number, userName: string): Promise { await this.addonStore.delete(id); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: events.ADDON_CONFIG_DELETED, createdBy: userName, data: { id }, diff --git a/src/lib/services/api-token-service.test.ts b/src/lib/services/api-token-service.test.ts index 0c0791d058..1e1f4b3952 100644 --- a/src/lib/services/api-token-service.test.ts +++ b/src/lib/services/api-token-service.test.ts @@ -11,6 +11,8 @@ import { API_TOKEN_UPDATED, } from '../types'; import { addDays } from 'date-fns'; +import EventService from './event-service'; +import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store'; test('Should init api token', async () => { const token = { @@ -28,16 +30,24 @@ test('Should init api token', async () => { }); const apiTokenStore = new FakeApiTokenStore(); const environmentStore = new FakeEnvironmentStore(); - const eventStore = new FakeEventStore(); const insertCalled = new Promise((resolve) => { apiTokenStore.on('insert', resolve); }); - new ApiTokenService( - { apiTokenStore, environmentStore, eventStore }, + const eventService = new EventService( + { + eventStore: new FakeEventStore(), + featureTagStore: new FakeFeatureTagStore(), + }, config, ); + new ApiTokenService( + { apiTokenStore, environmentStore }, + config, + eventService, + ); + await insertCalled; const tokens = await apiTokenStore.getAll(); @@ -58,7 +68,14 @@ test("Shouldn't return frontend token when secret is undefined", async () => { const config: IUnleashConfig = createTestConfig({}); const apiTokenStore = new FakeApiTokenStore(); const environmentStore = new FakeEnvironmentStore(); - const eventStore = new FakeEventStore(); + + const eventService = new EventService( + { + eventStore: new FakeEventStore(), + featureTagStore: new FakeFeatureTagStore(), + }, + config, + ); await environmentStore.create({ name: 'default', @@ -69,8 +86,9 @@ test("Shouldn't return frontend token when secret is undefined", async () => { }); const apiTokenService = new ApiTokenService( - { apiTokenStore, environmentStore, eventStore }, + { apiTokenStore, environmentStore }, config, + eventService, ); await apiTokenService.createApiTokenWithProjects(token); @@ -93,7 +111,14 @@ test('Api token operations should all have events attached', async () => { const config: IUnleashConfig = createTestConfig({}); const apiTokenStore = new FakeApiTokenStore(); const environmentStore = new FakeEnvironmentStore(); - const eventStore = new FakeEventStore(); + + const eventService = new EventService( + { + eventStore: new FakeEventStore(), + featureTagStore: new FakeFeatureTagStore(), + }, + config, + ); await environmentStore.create({ name: 'default', @@ -104,14 +129,15 @@ test('Api token operations should all have events attached', async () => { }); const apiTokenService = new ApiTokenService( - { apiTokenStore, environmentStore, eventStore }, + { apiTokenStore, environmentStore }, config, + eventService, ); let saved = await apiTokenService.createApiTokenWithProjects(token); let newExpiry = addDays(new Date(), 30); await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test'); await apiTokenService.delete(saved.secret, 'test'); - const events = await eventStore.getEvents(); + const { events } = await eventService.getEvents(); const createdApiTokenEvents = events.filter( (e) => e.type === API_TOKEN_CREATED, ); diff --git a/src/lib/services/api-token-service.ts b/src/lib/services/api-token-service.ts index 24b050b490..0c454c3433 100644 --- a/src/lib/services/api-token-service.ts +++ b/src/lib/services/api-token-service.ts @@ -1,7 +1,7 @@ import crypto from 'crypto'; import { Logger } from '../logger'; import { ADMIN, CLIENT, FRONTEND } from '../types/permissions'; -import { IEventStore, IUnleashStores } from '../types/stores'; +import { IUnleashStores } from '../types/stores'; import { IUnleashConfig } from '../types/option'; import ApiUser from '../types/api-user'; import { @@ -25,6 +25,7 @@ import { ApiTokenUpdatedEvent, } from '../types'; import { omitKeys } from '../util'; +import EventService from './event-service'; const resolveTokenPermissions = (tokenType: string) => { if (tokenType === ApiTokenType.ADMIN) { @@ -51,7 +52,7 @@ export class ApiTokenService { private activeTokens: IApiToken[] = []; - private eventStore: IEventStore; + private eventService: EventService; private lastSeenSecrets: Set = new Set(); @@ -59,15 +60,12 @@ export class ApiTokenService { { apiTokenStore, environmentStore, - eventStore, - }: Pick< - IUnleashStores, - 'apiTokenStore' | 'environmentStore' | 'eventStore' - >, + }: Pick, config: Pick, + eventService: EventService, ) { this.store = apiTokenStore; - this.eventStore = eventStore; + this.eventService = eventService; this.environmentStore = environmentStore; this.logger = config.getLogger('/services/api-token-service.ts'); this.fetchActiveTokens(); @@ -167,7 +165,7 @@ export class ApiTokenService { ): Promise { const previous = await this.store.get(secret); const token = await this.store.setExpiry(secret, expiresAt); - await this.eventStore.store( + await this.eventService.storeEvent( new ApiTokenUpdatedEvent({ createdBy: updatedBy, previousToken: omitKeys(previous, 'secret'), @@ -181,7 +179,7 @@ export class ApiTokenService { if (await this.store.exists(secret)) { const token = await this.store.get(secret); await this.store.delete(secret); - await this.eventStore.store( + await this.eventService.storeEvent( new ApiTokenDeletedEvent({ createdBy: deletedBy, apiToken: omitKeys(token, 'secret'), @@ -233,7 +231,7 @@ export class ApiTokenService { try { const token = await this.store.insert(newApiToken); this.activeTokens.push(token); - await this.eventStore.store( + await this.eventService.storeEvent( new ApiTokenCreatedEvent({ createdBy, apiToken: omitKeys(token, 'secret'), diff --git a/src/lib/services/context-service.ts b/src/lib/services/context-service.ts index 775bd893ce..04e54322f4 100644 --- a/src/lib/services/context-service.ts +++ b/src/lib/services/context-service.ts @@ -4,13 +4,13 @@ import { IContextFieldDto, IContextFieldStore, } from '../types/stores/context-field-store'; -import { IEventStore } from '../types/stores/event-store'; import { IProjectStore } from '../types/stores/project-store'; import { IFeatureStrategiesStore, IUnleashStores } from '../types/stores'; import { IUnleashConfig } from '../types/option'; import { ContextFieldStrategiesSchema } from '../openapi/spec/context-field-strategies-schema'; import { IFeatureStrategy, IFlagResolver } from '../types'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; +import EventService from './event-service'; const { contextSchema, nameSchema } = require('./context-schema'); const NameExistsError = require('../error/name-exists-error'); @@ -24,7 +24,7 @@ const { class ContextService { private projectStore: IProjectStore; - private eventStore: IEventStore; + private eventService: EventService; private contextFieldStore: IContextFieldStore; @@ -39,25 +39,22 @@ class ContextService { constructor( { projectStore, - eventStore, contextFieldStore, featureStrategiesStore, }: Pick< IUnleashStores, - | 'projectStore' - | 'eventStore' - | 'contextFieldStore' - | 'featureStrategiesStore' + 'projectStore' | 'contextFieldStore' | 'featureStrategiesStore' >, { getLogger, flagResolver, }: Pick, + eventService: EventService, privateProjectChecker: IPrivateProjectChecker, ) { this.privateProjectChecker = privateProjectChecker; this.projectStore = projectStore; - this.eventStore = eventStore; + this.eventService = eventService; this.flagResolver = flagResolver; this.contextFieldStore = contextFieldStore; this.featureStrategiesStore = featureStrategiesStore; @@ -120,7 +117,7 @@ class ContextService { // creations const createdField = await this.contextFieldStore.create(value); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: CONTEXT_FIELD_CREATED, createdBy: userName, data: contextField, @@ -139,7 +136,7 @@ class ContextService { // update await this.contextFieldStore.update(value); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: CONTEXT_FIELD_UPDATED, createdBy: userName, data: value, @@ -152,7 +149,7 @@ class ContextService { // delete await this.contextFieldStore.delete(name); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: CONTEXT_FIELD_DELETED, createdBy: userName, data: { name }, diff --git a/src/lib/services/event-service.ts b/src/lib/services/event-service.ts index d8dd290628..82f641e4d8 100644 --- a/src/lib/services/event-service.ts +++ b/src/lib/services/event-service.ts @@ -1,21 +1,29 @@ import { IUnleashConfig } from '../types/option'; -import { IUnleashStores } from '../types/stores'; +import { IFeatureTagStore, IUnleashStores } from '../types/stores'; import { Logger } from '../logger'; import { IEventStore } from '../types/stores/event-store'; -import { IEventList } from '../types/events'; +import { IBaseEvent, IEventList } from '../types/events'; import { SearchEventsSchema } from '../openapi/spec/search-events-schema'; +import EventEmitter from 'events'; +import { ITag } from '../types'; export default class EventService { private logger: Logger; private eventStore: IEventStore; + private featureTagStore: IFeatureTagStore; + constructor( - { eventStore }: Pick, + { + eventStore, + featureTagStore, + }: Pick, { getLogger }: Pick, ) { this.logger = getLogger('services/event-service.ts'); this.eventStore = eventStore; + this.featureTagStore = featureTagStore; } async getEvents(): Promise { @@ -35,4 +43,53 @@ export default class EventService { totalEvents, }; } + + async onEvent( + eventName: string | symbol, + listener: (...args: any[]) => void, + ): Promise { + return this.eventStore.on(eventName, listener); + } + + private async enhanceEventsWithTags( + events: IBaseEvent[], + ): Promise { + const featureNamesSet = new Set(); + for (const event of events) { + if (event.featureName && !event.tags) { + featureNamesSet.add(event.featureName); + } + } + + const featureTagsMap: Map = new Map(); + const allTagsInFeatures = await this.featureTagStore.getAllByFeatures( + Array.from(featureNamesSet), + ); + + for (const tag of allTagsInFeatures) { + const featureTags = featureTagsMap.get(tag.featureName) || []; + featureTags.push({ value: tag.tagValue, type: tag.tagType }); + featureTagsMap.set(tag.featureName, featureTags); + } + + for (const event of events) { + if (event.featureName && !event.tags) { + event.tags = featureTagsMap.get(event.featureName); + } + } + + return events; + } + + async storeEvent(event: IBaseEvent): Promise { + return this.storeEvents([event]); + } + + async storeEvents(events: IBaseEvent[]): Promise { + let enhancedEvents = events; + for (const enhancer of [this.enhanceEventsWithTags.bind(this)]) { + enhancedEvents = await enhancer(enhancedEvents); + } + return this.eventStore.batchStore(enhancedEvents); + } } diff --git a/src/lib/services/favorites-service.ts b/src/lib/services/favorites-service.ts index d9aad26dbc..f6f30030d6 100644 --- a/src/lib/services/favorites-service.ts +++ b/src/lib/services/favorites-service.ts @@ -1,9 +1,5 @@ import { IUnleashConfig } from '../types/option'; -import { - IEventStore, - IFavoriteProjectsStore, - IUnleashStores, -} from '../types/stores'; +import { IFavoriteProjectsStore, IUnleashStores } from '../types/stores'; import { Logger } from '../logger'; import { IFavoriteFeaturesStore } from '../types/stores/favorite-features'; import { IFavoriteFeature, IFavoriteProject } from '../types/favorites'; @@ -16,6 +12,7 @@ import { import User from '../types/user'; import { extractUsernameFromUser } from '../util'; import { IFavoriteProjectKey } from '../types/stores/favorite-projects'; +import EventService from './event-service'; export interface IFavoriteFeatureProps { feature: string; @@ -36,24 +33,24 @@ export class FavoritesService { private favoriteProjectsStore: IFavoriteProjectsStore; - private eventStore: IEventStore; + private eventService: EventService; constructor( { favoriteFeaturesStore, favoriteProjectsStore, - eventStore, }: Pick< IUnleashStores, - 'favoriteFeaturesStore' | 'favoriteProjectsStore' | 'eventStore' + 'favoriteFeaturesStore' | 'favoriteProjectsStore' >, config: IUnleashConfig, + eventService: EventService, ) { this.config = config; this.logger = config.getLogger('services/favorites-service.ts'); this.favoriteFeaturesStore = favoriteFeaturesStore; this.favoriteProjectsStore = favoriteProjectsStore; - this.eventStore = eventStore; + this.eventService = eventService; } async favoriteFeature({ @@ -64,8 +61,9 @@ export class FavoritesService { feature: feature, userId: user.id, }); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: FEATURE_FAVORITED, + featureName: feature, createdBy: extractUsernameFromUser(user), data: { feature, @@ -82,8 +80,9 @@ export class FavoritesService { feature: feature, userId: user.id, }); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: FEATURE_UNFAVORITED, + featureName: feature, createdBy: extractUsernameFromUser(user), data: { feature, @@ -100,7 +99,7 @@ export class FavoritesService { project, userId: user.id, }); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: PROJECT_FAVORITED, createdBy: extractUsernameFromUser(user), data: { @@ -118,7 +117,7 @@ export class FavoritesService { project: project, userId: user.id, }); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: PROJECT_UNFAVORITED, createdBy: extractUsernameFromUser(user), data: { diff --git a/src/lib/services/feature-service-potentially-stale.test.ts b/src/lib/services/feature-service-potentially-stale.test.ts index 8ae7848e5f..2a7a9f9dfa 100644 --- a/src/lib/services/feature-service-potentially-stale.test.ts +++ b/src/lib/services/feature-service-potentially-stale.test.ts @@ -11,6 +11,8 @@ import { IChangeRequestAccessReadModel } from 'lib/features/change-request-acces import { ISegmentService } from 'lib/segments/segment-service-interface'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type'; +import EventService from './event-service'; +import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store'; test('Should only store events for potentially stale on', async () => { expect.assertions(2); @@ -20,16 +22,11 @@ test('Should only store events for potentially stale on', async () => { ]; const config = createTestConfig(); - const featureToggleService = new FeatureToggleService( + const eventService = new EventService( { - featureToggleStore: { - updatePotentiallyStaleFeatures: () => featureUpdates, - }, - featureTagStore: { - getAllTagsForFeature: () => [], - }, + // @ts-expect-error eventStore: { - batchStore: (events: IEvent[]) => { + batchStore: async (events: IEvent[]) => { expect(events.length).toBe(1); const [event1] = events; @@ -40,6 +37,19 @@ test('Should only store events for potentially stale on', async () => { }); }, }, + featureTagStore: new FakeFeatureTagStore(), + }, + config, + ); + + const featureToggleService = new FeatureToggleService( + { + featureToggleStore: { + updatePotentiallyStaleFeatures: () => featureUpdates, + }, + featureTagStore: { + getAllTagsForFeature: () => [], + }, } as unknown as IUnleashStores, { ...config, @@ -50,6 +60,7 @@ test('Should only store events for potentially stale on', async () => { } as unknown as IUnleashConfig, {} as ISegmentService, {} as AccessService, + eventService, {} as IChangeRequestAccessReadModel, {} as IPrivateProjectChecker, {} as IDependentFeaturesReadModel, diff --git a/src/lib/services/feature-tag-service.ts b/src/lib/services/feature-tag-service.ts index da5b2ce5e3..025b85cce6 100644 --- a/src/lib/services/feature-tag-service.ts +++ b/src/lib/services/feature-tag-service.ts @@ -8,10 +8,10 @@ import { IFeatureTag, IFeatureTagStore, } from '../types/stores/feature-tag-store'; -import { IEventStore } from '../types/stores/event-store'; import { ITagStore } from '../types/stores/tag-store'; import { ITag } from '../types/model'; import { BadDataError, FOREIGN_KEY_VIOLATION } from '../../lib/error'; +import EventService from './event-service'; class FeatureTagService { private tagStore: ITagStore; @@ -20,7 +20,7 @@ class FeatureTagService { private featureToggleStore: IFeatureToggleStore; - private eventStore: IEventStore; + private eventService: EventService; private logger: Logger; @@ -28,19 +28,19 @@ class FeatureTagService { { tagStore, featureTagStore, - eventStore, featureToggleStore, }: Pick< IUnleashStores, - 'tagStore' | 'featureTagStore' | 'eventStore' | 'featureToggleStore' + 'tagStore' | 'featureTagStore' | 'featureToggleStore' >, { getLogger }: Pick, + eventService: EventService, ) { this.logger = getLogger('/services/feature-tag-service.ts'); this.tagStore = tagStore; this.featureTagStore = featureTagStore; this.featureToggleStore = featureToggleStore; - this.eventStore = eventStore; + this.eventService = eventService; } async listTags(featureName: string): Promise { @@ -62,7 +62,7 @@ class FeatureTagService { await this.createTagIfNeeded(validatedTag, userName); await this.featureTagStore.tagFeature(featureName, validatedTag); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: FEATURE_TAGGED, createdBy: userName, featureName, @@ -126,7 +126,10 @@ class FeatureTagService { })), ); - await this.eventStore.batchStore([...creationEvents, ...removalEvents]); + await this.eventService.storeEvents([ + ...creationEvents, + ...removalEvents, + ]); } async createTagIfNeeded(tag: ITag, userName: string): Promise { @@ -136,7 +139,7 @@ class FeatureTagService { if (error instanceof NotFoundError) { try { await this.tagStore.createTag(tag); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: TAG_CREATED, createdBy: userName, data: tag, @@ -159,13 +162,17 @@ class FeatureTagService { userName: string, ): Promise { const featureToggle = await this.featureToggleStore.get(featureName); + const tags = await this.featureTagStore.getAllTagsForFeature( + featureName, + ); await this.featureTagStore.untagFeature(featureName, tag); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: FEATURE_UNTAGGED, createdBy: userName, featureName, project: featureToggle.project, data: tag, + tags, }); } } diff --git a/src/lib/services/feature-toggle-service.ts b/src/lib/services/feature-toggle-service.ts index 12a54bef96..33c9df1483 100644 --- a/src/lib/services/feature-toggle-service.ts +++ b/src/lib/services/feature-toggle-service.ts @@ -21,7 +21,6 @@ import { FeatureVariantEvent, IConstraint, IDependency, - IEventStore, IFeatureEnvironmentInfo, IFeatureEnvironmentStore, IFeatureNaming, @@ -99,6 +98,7 @@ import { IChangeRequestAccessReadModel } from '../features/change-request-access import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-pattern/feature-naming-validation'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type'; +import EventService from './event-service'; interface IFeatureContext { featureName: string; @@ -146,14 +146,14 @@ class FeatureToggleService { private projectStore: IProjectStore; - private eventStore: IEventStore; - private contextFieldStore: IContextFieldStore; private segmentService: ISegmentService; private accessService: AccessService; + private eventService: EventService; + private flagResolver: IFlagResolver; private changeRequestAccessReadModel: IChangeRequestAccessReadModel; @@ -168,7 +168,6 @@ class FeatureToggleService { featureToggleStore, featureToggleClientStore, projectStore, - eventStore, featureTagStore, featureEnvironmentStore, contextFieldStore, @@ -179,7 +178,6 @@ class FeatureToggleService { | 'featureToggleStore' | 'featureToggleClientStore' | 'projectStore' - | 'eventStore' | 'featureTagStore' | 'featureEnvironmentStore' | 'contextFieldStore' @@ -191,6 +189,7 @@ class FeatureToggleService { }: Pick, segmentService: ISegmentService, accessService: AccessService, + eventService: EventService, changeRequestAccessReadModel: IChangeRequestAccessReadModel, privateProjectChecker: IPrivateProjectChecker, dependentFeaturesReadModel: IDependentFeaturesReadModel, @@ -202,11 +201,11 @@ class FeatureToggleService { this.featureToggleClientStore = featureToggleClientStore; this.tagStore = featureTagStore; this.projectStore = projectStore; - this.eventStore = eventStore; this.featureEnvironmentStore = featureEnvironmentStore; this.contextFieldStore = contextFieldStore; this.segmentService = segmentService; this.accessService = accessService; + this.eventService = eventService; this.flagResolver = flagResolver; this.changeRequestAccessReadModel = changeRequestAccessReadModel; this.privateProjectChecker = privateProjectChecker; @@ -394,15 +393,12 @@ class FeatureToggleService { ); if (featureToggle.stale !== newDocument.stale) { - const tags = await this.tagStore.getAllTagsForFeature(featureName); - - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureStaleEvent({ stale: newDocument.stale, project, featureName, createdBy, - tags, }), ); } @@ -502,7 +498,6 @@ class FeatureToggleService { const eventData: StrategyIds = { strategyIds: newOrder }; - const tags = await this.tagStore.getAllTagsForFeature(featureName); const event = new StrategiesOrderChangedEvent({ featureName, environment, @@ -510,9 +505,8 @@ class FeatureToggleService { createdBy, preData: eventPreData, data: eventData, - tags: tags, }); - await this.eventStore.store(event); + await this.eventService.storeEvent(event); } async createStrategy( @@ -606,16 +600,13 @@ class FeatureToggleService { segments, ); - const tags = await this.tagStore.getAllTagsForFeature(featureName); - - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureStrategyAddEvent({ project: projectId, featureName, createdBy, environment, data: strategy, - tags, }), ); return strategy; @@ -724,13 +715,12 @@ class FeatureToggleService { ); // Store event! - const tags = await this.tagStore.getAllTagsForFeature(featureName); const data = this.featureStrategyToPublic(strategy, segments); const preData = this.featureStrategyToPublic( existingStrategy, segments, ); - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureStrategyUpdateEvent({ project: projectId, featureName, @@ -738,7 +728,6 @@ class FeatureToggleService { createdBy: userName, data, preData, - tags, }), ); await this.optionallyDisableFeature( @@ -770,7 +759,6 @@ class FeatureToggleService { id, existingStrategy, ); - const tags = await this.tagStore.getAllTagsForFeature(featureName); const segments = await this.segmentService.getByStrategy( strategy.id, ); @@ -779,7 +767,7 @@ class FeatureToggleService { existingStrategy, segments, ); - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureStrategyUpdateEvent({ featureName, project: projectId, @@ -787,7 +775,6 @@ class FeatureToggleService { createdBy: userName, data, preData, - tags, }), ); return data; @@ -852,17 +839,15 @@ class FeatureToggleService { ); } - const tags = await this.tagStore.getAllTagsForFeature(featureName); const preData = this.featureStrategyToPublic(existingStrategy); - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureStrategyRemoveEvent({ featureName, project: projectId, environment, createdBy, preData, - tags, }), ); @@ -1125,15 +1110,12 @@ class FeatureToggleService { ); } - const tags = await this.tagStore.getAllTagsForFeature(featureName); - - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureCreatedEvent({ featureName, createdBy, project: projectId, data: createdToggle, - tags, }), ); @@ -1283,16 +1265,13 @@ class FeatureToggleService { name: featureName, }); - const tags = await this.tagStore.getAllTagsForFeature(featureName); - - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureMetadataUpdateEvent({ createdBy: userName, data: featureToggle, preData, featureName, project: projectId, - tags, }), ); return featureToggle; @@ -1419,15 +1398,13 @@ class FeatureToggleService { const { project } = feature; feature.stale = isStale; await this.featureToggleStore.update(project, feature); - const tags = await this.tagStore.getAllTagsForFeature(featureName); - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureStaleEvent({ stale: isStale, project, featureName, createdBy, - tags, }), ); @@ -1449,13 +1426,12 @@ class FeatureToggleService { } await this.featureToggleStore.archive(featureName); - const tags = await this.tagStore.getAllTagsForFeature(featureName); - await this.eventStore.store( + + await this.eventService.storeEvent( new FeatureArchivedEvent({ featureName, createdBy, project: feature.project, - tags, }), ); } @@ -1471,20 +1447,14 @@ class FeatureToggleService { featureNames, ); await this.featureToggleStore.batchArchive(featureNames); - const tags = await this.tagStore.getAllByFeatures(featureNames); - await this.eventStore.batchStore( + + await this.eventService.storeEvents( features.map( (feature) => new FeatureArchivedEvent({ featureName: feature.name, createdBy, project: feature.project, - tags: tags - .filter((tag) => tag.featureName === feature.name) - .map((tag) => ({ - value: tag.tagValue, - type: tag.tagType, - })), }), ), ); @@ -1508,8 +1478,8 @@ class FeatureToggleService { (feature) => feature.name, ); await this.featureToggleStore.batchStale(relevantFeatureNames, stale); - const tags = await this.tagStore.getAllByFeatures(relevantFeatureNames); - await this.eventStore.batchStore( + + await this.eventService.storeEvents( relevantFeatures.map( (feature) => new FeatureStaleEvent({ @@ -1517,12 +1487,6 @@ class FeatureToggleService { project: projectId, featureName: feature.name, createdBy, - tags: tags - .filter((tag) => tag.featureName === feature.name) - .map((tag) => ({ - value: tag.tagValue, - type: tag.tagType, - })), }), ), ); @@ -1667,15 +1631,13 @@ class FeatureToggleService { const feature = await this.featureToggleStore.get(featureName); if (updatedEnvironmentStatus > 0) { - const tags = await this.tagStore.getAllTagsForFeature(featureName); - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureEnvironmentEvent({ enabled, project, featureName, environment, createdBy, - tags, }), ); } @@ -1687,17 +1649,15 @@ class FeatureToggleService { featureName: string, createdBy: string, ): Promise { - const tags = await this.tagStore.getAllTagsForFeature(featureName); const feature = await this.getFeatureToggleLegacy(featureName); // Legacy event. Will not be used from v4.3. // We do not include 'preData' on purpose. - await this.eventStore.store({ + await this.eventService.storeEvent({ type: FEATURE_UPDATED, createdBy, featureName, data: feature, - tags, project: feature.project, }); return feature; @@ -1759,14 +1719,12 @@ class FeatureToggleService { feature.project = newProject; await this.featureToggleStore.update(newProject, feature); - const tags = await this.tagStore.getAllTagsForFeature(featureName); - await this.eventStore.store( + await this.eventService.storeEvent( new FeatureChangeProjectEvent({ createdBy, oldProject, newProject, featureName, - tags, }), ); } @@ -1780,7 +1738,8 @@ class FeatureToggleService { const toggle = await this.featureToggleStore.get(featureName); const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.featureToggleStore.delete(featureName); - await this.eventStore.store( + + await this.eventService.storeEvent( new FeatureDeletedEvent({ featureName, project: toggle.project, @@ -1809,7 +1768,8 @@ class FeatureToggleService { ); const tags = await this.tagStore.getAllByFeatures(eligibleFeatureNames); await this.featureToggleStore.batchDelete(eligibleFeatureNames); - await this.eventStore.batchStore( + + await this.eventService.storeEvents( eligibleFeatures.map( (feature) => new FeatureDeletedEvent({ @@ -1844,21 +1804,15 @@ class FeatureToggleService { const eligibleFeatureNames = eligibleFeatures.map( (toggle) => toggle.name, ); - const tags = await this.tagStore.getAllByFeatures(eligibleFeatureNames); await this.featureToggleStore.batchRevive(eligibleFeatureNames); - await this.eventStore.batchStore( + + await this.eventService.storeEvents( eligibleFeatures.map( (feature) => new FeatureRevivedEvent({ featureName: feature.name, createdBy, project: feature.project, - tags: tags - .filter((tag) => tag.featureName === feature.name) - .map((tag) => ({ - value: tag.tagValue, - type: tag.tagType, - })), }), ), ); @@ -1867,13 +1821,12 @@ class FeatureToggleService { // TODO: add project id. async reviveFeature(featureName: string, createdBy: string): Promise { const toggle = await this.featureToggleStore.revive(featureName); - const tags = await this.tagStore.getAllTagsForFeature(featureName); - await this.eventStore.store( + + await this.eventService.storeEvent( new FeatureRevivedEvent({ createdBy, featureName, project: toggle.project, - tags, }), ); } @@ -1985,13 +1938,12 @@ class FeatureToggleService { featureName, fixedVariants, ); - const tags = await this.tagStore.getAllTagsForFeature(featureName); - await this.eventStore.store( + + await this.eventService.storeEvent( new FeatureVariantEvent({ project, featureName, createdBy, - tags, oldVariants, newVariants: featureToggle.variants as IVariant[], }), @@ -2019,9 +1971,7 @@ class FeatureToggleService { ).variants || []; - const tags = await this.tagStore.getAllTagsForFeature(featureName); - - await this.eventStore.store( + await this.eventService.storeEvent( new EnvironmentVariantEvent({ featureName, environment, @@ -2029,7 +1979,6 @@ class FeatureToggleService { createdBy: user, oldVariants: theOldVariants, newVariants: fixedVariants, - tags, }), ); await this.featureEnvironmentStore.setVariantsToFeatureEnvironments( @@ -2096,9 +2045,7 @@ class FeatureToggleService { oldVariants[env] = featureEnv.variants || []; } - const tags = await this.tagStore.getAllTagsForFeature(featureName); - - await this.eventStore.batchStore( + await this.eventService.storeEvents( environments.map( (environment) => new EnvironmentVariantEvent({ @@ -2108,7 +2055,6 @@ class FeatureToggleService { createdBy: user, oldVariants: oldVariants[environment], newVariants: fixedVariants, - tags, }), ), ); @@ -2214,22 +2160,18 @@ class FeatureToggleService { async updatePotentiallyStaleFeatures(): Promise { const potentiallyStaleFeatures = await this.featureToggleStore.updatePotentiallyStaleFeatures(); + if (potentiallyStaleFeatures.length > 0) { - return this.eventStore.batchStore( - await Promise.all( - potentiallyStaleFeatures - .filter((feature) => feature.potentiallyStale) - .map( - async ({ name, project }) => - new PotentiallyStaleOnEvent({ - featureName: name, - project, - tags: await this.tagStore.getAllTagsForFeature( - name, - ), - }), - ), - ), + return this.eventService.storeEvents( + potentiallyStaleFeatures + .filter((feature) => feature.potentiallyStale) + .map( + ({ name, project }) => + new PotentiallyStaleOnEvent({ + featureName: name, + project, + }), + ), ); } } diff --git a/src/lib/services/group-service.ts b/src/lib/services/group-service.ts index ea4e652252..0129885d9a 100644 --- a/src/lib/services/group-service.ts +++ b/src/lib/services/group-service.ts @@ -12,30 +12,28 @@ import { IGroupStore } from '../types/stores/group-store'; import { Logger } from '../logger'; import BadDataError from '../error/bad-data-error'; import { GROUP_CREATED, GROUP_DELETED, GROUP_UPDATED } from '../types/events'; -import { IEventStore } from '../types/stores/event-store'; import NameExistsError from '../error/name-exists-error'; import { IAccountStore } from '../types/stores/account-store'; import { IUser } from '../types/user'; +import EventService from './event-service'; export class GroupService { private groupStore: IGroupStore; - private eventStore: IEventStore; + private eventService: EventService; private accountStore: IAccountStore; private logger: Logger; constructor( - stores: Pick< - IUnleashStores, - 'groupStore' | 'eventStore' | 'accountStore' - >, + stores: Pick, { getLogger }: Pick, + eventService: EventService, ) { this.logger = getLogger('service/group-service.js'); this.groupStore = stores.groupStore; - this.eventStore = stores.eventStore; + this.eventService = eventService; this.accountStore = stores.accountStore; } @@ -96,7 +94,7 @@ export class GroupService { userName, ); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: GROUP_CREATED, createdBy: userName, data: group, @@ -133,7 +131,7 @@ export class GroupService { userName, ); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: GROUP_UPDATED, createdBy: userName, data: newGroup, @@ -175,7 +173,7 @@ export class GroupService { await this.groupStore.delete(id); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: GROUP_DELETED, createdBy: userName, data: group, diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index b14fba39a0..7bb76e1d81 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -165,9 +165,10 @@ export const createServices = ( config: IUnleashConfig, db?: Db, ): IUnleashServices => { - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); const accessService = new AccessService(stores, config, groupService); - const apiTokenService = new ApiTokenService(stores, config); + const apiTokenService = new ApiTokenService(stores, config, eventService); const lastSeenService = new LastSeenService(stores, config); const clientMetricsServiceV2 = new ClientMetricsServiceV2( stores, @@ -184,23 +185,29 @@ export const createServices = ( const contextService = new ContextService( stores, config, + eventService, privateProjectChecker, ); const emailService = new EmailService(config.email, config.getLogger); - const eventService = new EventService(stores, config); const featureTypeService = new FeatureTypeService(stores, config); const resetTokenService = new ResetTokenService(stores, config); - const stateService = new StateService(stores, config); - const strategyService = new StrategyService(stores, config); - const tagService = new TagService(stores, config); - const tagTypeService = new TagTypeService(stores, config); - const addonService = new AddonService(stores, config, tagTypeService); + const stateService = new StateService(stores, config, eventService); + const strategyService = new StrategyService(stores, config, eventService); + const tagService = new TagService(stores, config, eventService); + const tagTypeService = new TagTypeService(stores, config, eventService); + const addonService = new AddonService( + stores, + config, + tagTypeService, + eventService, + ); const sessionService = new SessionService(stores, config); - const settingService = new SettingService(stores, config); + const settingService = new SettingService(stores, config, eventService); const userService = new UserService(stores, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -217,6 +224,7 @@ export const createServices = ( stores, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ); @@ -230,13 +238,18 @@ export const createServices = ( config, segmentService, accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, ); const environmentService = new EnvironmentService(stores, config); - const featureTagService = new FeatureTagService(stores, config); - const favoritesService = new FavoritesService(stores, config); + const featureTagService = new FeatureTagService( + stores, + config, + eventService, + ); + const favoritesService = new FavoritesService(stores, config, eventService); const projectService = new ProjectService( stores, config, @@ -244,6 +257,7 @@ export const createServices = ( featureToggleServiceV2, groupService, favoritesService, + eventService, privateProjectChecker, ); const projectHealthService = new ProjectHealthService( @@ -286,12 +300,13 @@ export const createServices = ( const edgeService = new EdgeService(stores, config); - const patService = new PatService(stores, config); + const patService = new PatService(stores, config, eventService); const publicSignupTokenService = new PublicSignupTokenService( stores, config, userService, + eventService, ); const instanceStatsService = new InstanceStatsService( diff --git a/src/lib/services/pat-service.ts b/src/lib/services/pat-service.ts index b9d33680c0..976e059006 100644 --- a/src/lib/services/pat-service.ts +++ b/src/lib/services/pat-service.ts @@ -1,7 +1,6 @@ import { IUnleashConfig, IUnleashStores } from '../types'; import { Logger } from '../logger'; import { IPatStore } from '../types/stores/pat-store'; -import { IEventStore } from '../types/stores/event-store'; import { PAT_CREATED, PAT_DELETED } from '../types/events'; import { IPat } from '../types/models/pat'; import crypto from 'crypto'; @@ -10,6 +9,7 @@ import BadDataError from '../error/bad-data-error'; import NameExistsError from '../error/name-exists-error'; import { OperationDeniedError } from '../error/operation-denied-error'; import { PAT_LIMIT } from '../util/constants'; +import EventService from './event-service'; export default class PatService { private config: IUnleashConfig; @@ -18,19 +18,17 @@ export default class PatService { private patStore: IPatStore; - private eventStore: IEventStore; + private eventService: EventService; constructor( - { - patStore, - eventStore, - }: Pick, + { patStore }: Pick, config: IUnleashConfig, + eventService: EventService, ) { this.config = config; this.logger = config.getLogger('services/pat-service.ts'); this.patStore = patStore; - this.eventStore = eventStore; + this.eventService = eventService; } async createPat(pat: IPat, forUserId: number, editor: User): Promise { @@ -40,7 +38,7 @@ export default class PatService { const newPat = await this.patStore.create(pat); pat.secret = '***'; - await this.eventStore.store({ + await this.eventService.storeEvent({ type: PAT_CREATED, createdBy: editor.email || editor.username, data: pat, @@ -61,7 +59,7 @@ export default class PatService { const pat = await this.patStore.get(id); pat.secret = '***'; - await this.eventStore.store({ + await this.eventService.storeEvent({ type: PAT_DELETED, createdBy: editor.email || editor.username, data: pat, diff --git a/src/lib/services/project-service.ts b/src/lib/services/project-service.ts index 016bbc1f8e..019e870028 100644 --- a/src/lib/services/project-service.ts +++ b/src/lib/services/project-service.ts @@ -64,6 +64,7 @@ import { BadDataError, PermissionError } from '../error'; import { ProjectDoraMetricsSchema } from 'lib/openapi'; import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; +import EventService from './event-service'; const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown'; @@ -113,6 +114,8 @@ export default class ProjectService { private favoritesService: FavoritesService; + private eventService: EventService; + private projectStatsStore: IProjectStatsStore; private flagResolver: IFlagResolver; @@ -145,6 +148,7 @@ export default class ProjectService { featureToggleService: FeatureToggleService, groupService: GroupService, favoriteService: FavoritesService, + eventService: EventService, privateProjectChecker: IPrivateProjectChecker, ) { this.projectStore = projectStore; @@ -159,6 +163,7 @@ export default class ProjectService { this.privateProjectChecker = privateProjectChecker; this.accountStore = accountStore; this.groupService = groupService; + this.eventService = eventService; this.projectStatsStore = projectStatsStore; this.logger = config.getLogger('services/project-service.js'); this.flagResolver = config.flagResolver; @@ -246,7 +251,7 @@ export default class ProjectService { await this.accessService.createDefaultProjectRoles(user, data.id); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: PROJECT_CREATED, createdBy: getCreatedBy(user), data, @@ -284,7 +289,7 @@ export default class ProjectService { await this.projectStore.updateProjectEnterpriseSettings(updatedProject); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: PROJECT_UPDATED, project: updatedProject.id, createdBy: getCreatedBy(user), @@ -381,7 +386,7 @@ export default class ProjectService { await this.projectStore.delete(id); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: PROJECT_DELETED, createdBy: getCreatedBy(user), project: id, @@ -434,7 +439,7 @@ export default class ProjectService { await this.accessService.addUserToRole(userId, role.id, projectId); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectUserAddedEvent({ project: projectId, createdBy: createdBy || 'system-user', @@ -462,7 +467,7 @@ export default class ProjectService { const user = await this.accountStore.get(userId); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectUserRemovedEvent({ project: projectId, createdBy, @@ -488,7 +493,7 @@ export default class ProjectService { await this.accessService.removeUserAccess(projectId, userId); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectAccessUserRolesDeleted({ project: projectId, createdBy, @@ -512,7 +517,7 @@ export default class ProjectService { await this.accessService.removeGroupAccess(projectId, groupId); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectAccessUserRolesDeleted({ project: projectId, createdBy, @@ -547,7 +552,7 @@ export default class ProjectService { project.id, ); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectGroupAddedEvent({ project: project.id, createdBy: modifiedBy, @@ -582,7 +587,7 @@ export default class ProjectService { project.id, ); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectGroupRemovedEvent({ project: projectId, createdBy: modifiedBy, @@ -609,7 +614,7 @@ export default class ProjectService { createdBy, ); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectAccessAddedEvent({ project: projectId, createdBy, @@ -637,7 +642,7 @@ export default class ProjectService { createdBy, ); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectAccessAddedEvent({ project: projectId, createdBy, @@ -665,7 +670,7 @@ export default class ProjectService { userId, roles, ); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectAccessUserRolesUpdated({ project: projectId, createdBy: createdByUserName, @@ -697,7 +702,7 @@ export default class ProjectService { roles, createdBy, ); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectAccessGroupRolesUpdated({ project: projectId, createdBy, @@ -823,7 +828,7 @@ export default class ProjectService { ); const role = await this.findProjectRole(projectId, roleId); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectUserUpdateRoleEvent({ project: projectId, createdBy, @@ -877,7 +882,7 @@ export default class ProjectService { ); const role = await this.findProjectGroupRole(projectId, roleId); - await this.eventStore.store( + await this.eventService.storeEvent( new ProjectGroupUpdateRoleEvent({ project: projectId, createdBy, diff --git a/src/lib/services/public-signup-token-service.ts b/src/lib/services/public-signup-token-service.ts index 88373ab2a4..3fd855cdb5 100644 --- a/src/lib/services/public-signup-token-service.ts +++ b/src/lib/services/public-signup-token-service.ts @@ -8,7 +8,6 @@ import { IPublicSignupTokenCreate } from '../types/models/public-signup-token'; import { PublicSignupTokenCreateSchema } from '../openapi/spec/public-signup-token-create-schema'; import { CreateInvitedUserSchema } from 'lib/openapi/spec/create-invited-user-schema'; import { RoleName } from '../types/model'; -import { IEventStore } from '../types/stores/event-store'; import { PublicSignupTokenCreatedEvent, PublicSignupTokenUpdatedEvent, @@ -18,16 +17,17 @@ import UserService from './user-service'; import { IUser } from '../types/user'; import { URL } from 'url'; import { add } from 'date-fns'; +import EventService from './event-service'; export class PublicSignupTokenService { private store: IPublicSignupTokenStore; private roleStore: IRoleStore; - private eventStore: IEventStore; - private userService: UserService; + private eventService: EventService; + private logger: Logger; private timer: NodeJS.Timeout; @@ -38,18 +38,15 @@ export class PublicSignupTokenService { { publicSignupTokenStore, roleStore, - eventStore, - }: Pick< - IUnleashStores, - 'publicSignupTokenStore' | 'roleStore' | 'eventStore' - >, + }: Pick, config: Pick, userService: UserService, + eventService: EventService, ) { this.store = publicSignupTokenStore; this.userService = userService; + this.eventService = eventService; this.roleStore = roleStore; - this.eventStore = eventStore; this.logger = config.getLogger( '/services/public-signup-token-service.ts', ); @@ -84,7 +81,7 @@ export class PublicSignupTokenService { createdBy: string, ): Promise { const result = await this.store.update(secret, { expiresAt, enabled }); - await this.eventStore.store( + await this.eventService.storeEvent( new PublicSignupTokenUpdatedEvent({ createdBy, data: { secret, enabled, expiresAt }, @@ -103,7 +100,7 @@ export class PublicSignupTokenService { rootRole: token.role.id, }); await this.store.addTokenUser(secret, user.id); - await this.eventStore.store( + await this.eventService.storeEvent( new PublicSignupTokenUserAddedEvent({ createdBy: 'System', data: { secret, userId: user.id }, @@ -133,7 +130,7 @@ export class PublicSignupTokenService { }; const token = await this.store.insert(newToken); - await this.eventStore.store( + await this.eventService.storeEvent( new PublicSignupTokenCreatedEvent({ createdBy: createdBy, data: token, diff --git a/src/lib/services/segment-service.ts b/src/lib/services/segment-service.ts index 95fe027846..8a98387dfc 100644 --- a/src/lib/services/segment-service.ts +++ b/src/lib/services/segment-service.ts @@ -1,5 +1,4 @@ import { IUnleashConfig } from '../types/option'; -import { IEventStore } from '../types/stores/event-store'; import { IClientSegment, IFlagResolver, @@ -23,6 +22,7 @@ import { ISegmentService } from '../segments/segment-service-interface'; import { PermissionError } from '../error'; import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; +import EventService from './event-service'; export class SegmentService implements ISegmentService { private logger: Logger; @@ -31,32 +31,29 @@ export class SegmentService implements ISegmentService { private featureStrategiesStore: IFeatureStrategiesStore; - private eventStore: IEventStore; - private changeRequestAccessReadModel: IChangeRequestAccessReadModel; private config: IUnleashConfig; private flagResolver: IFlagResolver; + private eventService: EventService; + private privateProjectChecker: IPrivateProjectChecker; constructor( { segmentStore, featureStrategiesStore, - eventStore, - }: Pick< - IUnleashStores, - 'segmentStore' | 'featureStrategiesStore' | 'eventStore' - >, + }: Pick, changeRequestAccessReadModel: IChangeRequestAccessReadModel, config: IUnleashConfig, + eventService: EventService, privateProjectChecker: IPrivateProjectChecker, ) { this.segmentStore = segmentStore; this.featureStrategiesStore = featureStrategiesStore; - this.eventStore = eventStore; + this.eventService = eventService; this.changeRequestAccessReadModel = changeRequestAccessReadModel; this.privateProjectChecker = privateProjectChecker; this.logger = config.getLogger('services/segment-service.ts'); @@ -117,7 +114,7 @@ export class SegmentService implements ISegmentService { await this.validateName(input.name); const segment = await this.segmentStore.create(input, user); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: SEGMENT_CREATED, createdBy: user.email || user.username || 'unknown', data: segment, @@ -149,7 +146,7 @@ export class SegmentService implements ISegmentService { const segment = await this.segmentStore.update(id, input); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: SEGMENT_UPDATED, createdBy: user.email || user.username || 'unknown', data: segment, @@ -161,7 +158,7 @@ export class SegmentService implements ISegmentService { const segment = await this.segmentStore.get(id); await this.stopWhenChangeRequestsEnabled(segment.project, user); await this.segmentStore.delete(id); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: SEGMENT_DELETED, createdBy: user.email || user.username, data: segment, @@ -171,7 +168,7 @@ export class SegmentService implements ISegmentService { async unprotectedDelete(id: number, user: User): Promise { const segment = await this.segmentStore.get(id); await this.segmentStore.delete(id); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: SEGMENT_DELETED, createdBy: user.email || user.username, data: segment, diff --git a/src/lib/services/setting-service.ts b/src/lib/services/setting-service.ts index 0f2f64703b..ba3814bb95 100644 --- a/src/lib/services/setting-service.ts +++ b/src/lib/services/setting-service.ts @@ -2,12 +2,12 @@ import { IUnleashConfig } from '../types/option'; import { IUnleashStores } from '../types/stores'; import { Logger } from '../logger'; import { ISettingStore } from '../types/stores/settings-store'; -import { IEventStore } from '../types/stores/event-store'; import { SettingCreatedEvent, SettingDeletedEvent, SettingUpdatedEvent, } from '../types/events'; +import EventService from './event-service'; export default class SettingService { private config: IUnleashConfig; @@ -16,19 +16,17 @@ export default class SettingService { private settingStore: ISettingStore; - private eventStore: IEventStore; + private eventService: EventService; constructor( - { - settingStore, - eventStore, - }: Pick, + { settingStore }: Pick, config: IUnleashConfig, + eventService: EventService, ) { this.config = config; this.logger = config.getLogger('services/setting-service.ts'); this.settingStore = settingStore; - this.eventStore = eventStore; + this.eventService = eventService; } async get(id: string, defaultValue?: T): Promise { @@ -40,7 +38,7 @@ export default class SettingService { const exists = await this.settingStore.exists(id); if (exists) { await this.settingStore.updateRow(id, value); - await this.eventStore.store( + await this.eventService.storeEvent( new SettingUpdatedEvent({ createdBy, data: { id }, @@ -48,7 +46,7 @@ export default class SettingService { ); } else { await this.settingStore.insert(id, value); - await this.eventStore.store( + await this.eventService.storeEvent( new SettingCreatedEvent({ createdBy, data: { id }, @@ -59,7 +57,7 @@ export default class SettingService { async delete(id: string, createdBy: string): Promise { await this.settingStore.delete(id); - await this.eventStore.store( + await this.eventService.storeEvent( new SettingDeletedEvent({ createdBy, data: { diff --git a/src/lib/services/state-service.test.ts b/src/lib/services/state-service.test.ts index fc71f2dbb6..9d1b50aa60 100644 --- a/src/lib/services/state-service.test.ts +++ b/src/lib/services/state-service.test.ts @@ -13,14 +13,20 @@ import { } from '../types/events'; import { GLOBAL_ENV } from '../types/environment'; import variantsExportV3 from '../../test/examples/variantsexport_v3.json'; +import EventService from './event-service'; const oldExportExample = require('./state-service-export-v1.json'); function getSetup() { const stores = createStores(); + const eventService = new EventService(stores, { getLogger }); return { - stateService: new StateService(stores, { - getLogger, - }), + stateService: new StateService( + stores, + { + getLogger, + }, + eventService, + ), stores, }; } @@ -61,10 +67,15 @@ async function setupV3VariantsCompatibilityScenario( ], ); }); + const eventService = new EventService(stores, { getLogger }); return { - stateService: new StateService(stores, { - getLogger, - }), + stateService: new StateService( + stores, + { + getLogger, + }, + eventService, + ), stores, }; } @@ -576,9 +587,14 @@ test('Should export projects', async () => { test('exporting to new format works', async () => { const stores = createStores(); - const stateService = new StateService(stores, { - getLogger, - }); + const eventService = new EventService(stores, { getLogger }); + const stateService = new StateService( + stores, + { + getLogger, + }, + eventService, + ); await stores.projectStore.create({ id: 'fancy', name: 'extra', diff --git a/src/lib/services/state-service.ts b/src/lib/services/state-service.ts index 0a73a3139a..0d54c010f8 100644 --- a/src/lib/services/state-service.ts +++ b/src/lib/services/state-service.ts @@ -39,7 +39,6 @@ import { import { IProjectStore } from '../types/stores/project-store'; import { ITagType, ITagTypeStore } from '../types/stores/tag-type-store'; import { ITagStore } from '../types/stores/tag-store'; -import { IEventStore } from '../types/stores/event-store'; import { IStrategy, IStrategyStore } from '../types/stores/strategy-store'; import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store'; @@ -50,6 +49,7 @@ import { DEFAULT_ENV } from '../util/constants'; import { GLOBAL_ENV } from '../types/environment'; import { ISegmentStore } from '../types/stores/segment-store'; import { PartialSome } from '../types/partial'; +import EventService from './event-service'; export interface IBackupOption { includeFeatureToggles: boolean; @@ -76,7 +76,7 @@ export default class StateService { private strategyStore: IStrategyStore; - private eventStore: IEventStore; + private eventService: EventService; private tagStore: ITagStore; @@ -95,8 +95,9 @@ export default class StateService { constructor( stores: IUnleashStores, { getLogger }: Pick, + eventService: EventService, ) { - this.eventStore = stores.eventStore; + this.eventService = eventService; this.toggleStore = stores.featureToggleStore; this.strategyStore = stores.strategyStore; this.tagStore = stores.tagStore; @@ -369,7 +370,7 @@ export default class StateService { if (dropBeforeImport) { this.logger.info('Dropping existing feature toggles'); await this.toggleStore.deleteAll(); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: DROP_FEATURES, createdBy: userName, data: { name: 'all-features' }, @@ -387,7 +388,7 @@ export default class StateService { feature.project, this.enabledIn(feature.name, featureEnvironments), ); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: FEATURE_IMPORT, createdBy: userName, data: feature, @@ -411,7 +412,7 @@ export default class StateService { if (dropBeforeImport) { this.logger.info('Dropping existing strategies'); await this.strategyStore.dropCustomStrategies(); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: DROP_STRATEGIES, createdBy: userName, data: { name: 'all-strategies' }, @@ -424,7 +425,7 @@ export default class StateService { .filter(filterEqual(oldStrategies)) .map((strategy) => this.strategyStore.importStrategy(strategy).then(() => { - this.eventStore.store({ + this.eventService.storeEvent({ type: STRATEGY_IMPORT, createdBy: userName, data: strategy, @@ -448,7 +449,7 @@ export default class StateService { if (dropBeforeImport) { this.logger.info('Dropping existing environments'); await this.environmentStore.deleteAll(); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: DROP_ENVIRONMENTS, createdBy: userName, data: { name: 'all-environments' }, @@ -467,7 +468,7 @@ export default class StateService { createdBy: userName, data: env, })); - await this.eventStore.batchStore(importedEnvironmentEvents); + await this.eventService.storeEvents(importedEnvironmentEvents); } return importedEnvs; } @@ -487,7 +488,7 @@ export default class StateService { if (dropBeforeImport) { this.logger.info('Dropping existing projects'); await this.projectStore.deleteAll(); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: DROP_PROJECTS, createdBy: userName, data: { name: 'all-projects' }, @@ -508,7 +509,7 @@ export default class StateService { createdBy: userName, data: project, })); - await this.eventStore.batchStore(importedProjectEvents); + await this.eventService.storeEvents(importedProjectEvents); } } @@ -538,7 +539,7 @@ export default class StateService { await this.featureTagStore.deleteAll(); await this.tagStore.deleteAll(); await this.tagTypeStore.deleteAll(); - await this.eventStore.batchStore([ + await this.eventService.storeEvents([ { type: DROP_FEATURE_TAGS, createdBy: userName, @@ -601,7 +602,7 @@ export default class StateService { createdBy: userName, data: tag, })); - await this.eventStore.batchStore(importedFeatureTagEvents); + await this.eventService.storeEvents(importedFeatureTagEvents); } } @@ -626,7 +627,7 @@ export default class StateService { createdBy: userName, data: tag, })); - await this.eventStore.batchStore(importedTagEvents); + await this.eventService.storeEvents(importedTagEvents); } } @@ -650,7 +651,7 @@ export default class StateService { createdBy: userName, data: tagType, })); - await this.eventStore.batchStore(importedTagTypeEvents); + await this.eventService.storeEvents(importedTagTypeEvents); } } diff --git a/src/lib/services/strategy-service.ts b/src/lib/services/strategy-service.ts index a6c4dcd343..970a8f69d5 100644 --- a/src/lib/services/strategy-service.ts +++ b/src/lib/services/strategy-service.ts @@ -1,13 +1,13 @@ import { Logger } from '../logger'; import { IUnleashConfig } from '../types/option'; import { IUnleashStores } from '../types/stores'; -import { IEventStore } from '../types/stores/event-store'; import { IMinimalStrategy, IStrategy, IStrategyStore, } from '../types/stores/strategy-store'; import NotFoundError from '../error/notfound-error'; +import EventService from './event-service'; const strategySchema = require('./strategy-schema'); const NameExistsError = require('../error/name-exists-error'); @@ -24,17 +24,15 @@ class StrategyService { private strategyStore: IStrategyStore; - private eventStore: IEventStore; + private eventService: EventService; constructor( - { - strategyStore, - eventStore, - }: Pick, + { strategyStore }: Pick, { getLogger }: Pick, + eventService: EventService, ) { this.strategyStore = strategyStore; - this.eventStore = eventStore; + this.eventService = eventService; this.logger = getLogger('services/strategy-service.js'); } @@ -53,7 +51,7 @@ class StrategyService { const strategy = await this.strategyStore.get(strategyName); await this._validateEditable(strategy); await this.strategyStore.delete(strategyName); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: STRATEGY_DELETED, createdBy: userName, data: { @@ -69,7 +67,7 @@ class StrategyService { if (await this.strategyStore.exists(strategyName)) { // Check existence await this.strategyStore.deprecateStrategy({ name: strategyName }); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: STRATEGY_DEPRECATED, createdBy: userName, data: { @@ -89,7 +87,7 @@ class StrategyService { ): Promise { await this.strategyStore.get(strategyName); // Check existence await this.strategyStore.reactivateStrategy({ name: strategyName }); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: STRATEGY_REACTIVATED, createdBy: userName, data: { @@ -106,7 +104,7 @@ class StrategyService { strategy.deprecated = false; await this._validateStrategyName(strategy); await this.strategyStore.createStrategy(strategy); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: STRATEGY_CREATED, createdBy: userName, data: strategy, @@ -122,7 +120,7 @@ class StrategyService { const strategy = await this.strategyStore.get(input.name); await this._validateEditable(strategy); await this.strategyStore.updateStrategy(value); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: STRATEGY_UPDATED, createdBy: userName, data: value, diff --git a/src/lib/services/tag-service.ts b/src/lib/services/tag-service.ts index 6f7c62050e..fd41b38a60 100644 --- a/src/lib/services/tag-service.ts +++ b/src/lib/services/tag-service.ts @@ -5,25 +5,23 @@ import { Logger } from '../logger'; import { IUnleashStores } from '../types/stores'; import { IUnleashConfig } from '../types/option'; import { ITagStore } from '../types/stores/tag-store'; -import { IEventStore } from '../types/stores/event-store'; import { ITag } from '../types/model'; +import EventService from './event-service'; export default class TagService { private tagStore: ITagStore; - private eventStore: IEventStore; + private eventService: EventService; private logger: Logger; constructor( - { - tagStore, - eventStore, - }: Pick, + { tagStore }: Pick, { getLogger }: Pick, + eventService: EventService, ) { this.tagStore = tagStore; - this.eventStore = eventStore; + this.eventService = eventService; this.logger = getLogger('services/tag-service.js'); } @@ -55,7 +53,7 @@ export default class TagService { async createTag(tag: ITag, userName: string): Promise { const data = await this.validate(tag); await this.tagStore.createTag(data); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: TAG_CREATED, createdBy: userName, data, @@ -66,7 +64,7 @@ export default class TagService { async deleteTag(tag: ITag, userName: string): Promise { await this.tagStore.delete(tag); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: TAG_DELETED, createdBy: userName, data: tag, diff --git a/src/lib/services/tag-type-service.ts b/src/lib/services/tag-type-service.ts index a919b9dfd6..75c89fc5c9 100644 --- a/src/lib/services/tag-type-service.ts +++ b/src/lib/services/tag-type-service.ts @@ -11,25 +11,23 @@ import { import { Logger } from '../logger'; import { ITagType, ITagTypeStore } from '../types/stores/tag-type-store'; -import { IEventStore } from '../types/stores/event-store'; import { IUnleashConfig } from '../types/option'; +import EventService from './event-service'; export default class TagTypeService { private tagTypeStore: ITagTypeStore; - private eventStore: IEventStore; + private eventService: EventService; private logger: Logger; constructor( - { - tagTypeStore, - eventStore, - }: Pick, + { tagTypeStore }: Pick, { getLogger }: Pick, + eventService: EventService, ) { this.tagTypeStore = tagTypeStore; - this.eventStore = eventStore; + this.eventService = eventService; this.logger = getLogger('services/tag-type-service.js'); } @@ -50,7 +48,7 @@ export default class TagTypeService { )) as ITagType; await this.validateUnique(data.name); await this.tagTypeStore.createTagType(data); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: TAG_TYPE_CREATED, createdBy: userName || 'unleash-system', data, @@ -77,7 +75,7 @@ export default class TagTypeService { async deleteTagType(name: string, userName: string): Promise { await this.tagTypeStore.delete(name); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: TAG_TYPE_DELETED, createdBy: userName || 'unleash-system', data: { name }, @@ -90,7 +88,7 @@ export default class TagTypeService { ): Promise { const data = await tagTypeSchema.validateAsync(updatedTagType); await this.tagTypeStore.updateTagType(data); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: TAG_TYPE_UPDATED, createdBy: userName || 'unleash-system', data, diff --git a/src/lib/services/user-service.test.ts b/src/lib/services/user-service.test.ts index 8f8568e076..39ec7b4ecf 100644 --- a/src/lib/services/user-service.test.ts +++ b/src/lib/services/user-service.test.ts @@ -14,7 +14,8 @@ import User from '../types/user'; import FakeResetTokenStore from '../../test/fixtures/fake-reset-token-store'; import SettingService from './setting-service'; import FakeSettingStore from '../../test/fixtures/fake-setting-store'; -import FakeEventStore from '../../test/fixtures/fake-event-store'; +import EventService from './event-service'; +import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store'; const config: IUnleashConfig = createTestConfig(); @@ -32,18 +33,23 @@ test('Should create new user', async () => { const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); const emailService = new EmailService(config.email, config.getLogger); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -75,18 +81,23 @@ test('Should create default user', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -110,18 +121,23 @@ test('Should be a valid password', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -143,18 +159,23 @@ test('Password must be at least 10 chars', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -178,18 +199,23 @@ test('The password must contain at least one uppercase letter.', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -215,18 +241,23 @@ test('The password must contain at least one number', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -251,18 +282,23 @@ test('The password must contain at least one special character', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -287,18 +323,23 @@ test('Should be a valid password with special chars', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -320,18 +361,23 @@ test('Should send password reset email if user exists', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); @@ -369,18 +415,23 @@ test('Should throttle password reset email', async () => { const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new FakeSessionStore(); const sessionService = new SessionService({ sessionStore }, config); + const eventService = new EventService( + { eventStore, featureTagStore: new FakeFeatureTagStore() }, + config, + ); const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); - const service = new UserService({ userStore, eventStore }, config, { + const service = new UserService({ userStore }, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); diff --git a/src/lib/services/user-service.ts b/src/lib/services/user-service.ts index 2d42baa3b4..b4c372a54f 100644 --- a/src/lib/services/user-service.ts +++ b/src/lib/services/user-service.ts @@ -17,7 +17,6 @@ import SessionService from './session-service'; import { IUnleashStores } from '../types/stores'; import PasswordUndefinedError from '../error/password-undefined'; import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../types/events'; -import { IEventStore } from '../types/stores/event-store'; import { IUserStore } from '../types/stores/user-store'; import { RoleName } from '../types/model'; import SettingService from './setting-service'; @@ -28,6 +27,7 @@ import BadDataError from '../error/bad-data-error'; import { isDefined } from '../util/isDefined'; import { TokenUserSchema } from '../openapi/spec/token-user-schema'; import PasswordMismatch from '../error/password-mismatch'; +import EventService from './event-service'; const systemUser = new User({ id: -1, username: 'system' }); @@ -64,7 +64,7 @@ class UserService { private store: IUserStore; - private eventStore: IEventStore; + private eventService: EventService; private accessService: AccessService; @@ -81,7 +81,7 @@ class UserService { private baseUriPath: string; constructor( - stores: Pick, + stores: Pick, { server, getLogger, @@ -91,13 +91,14 @@ class UserService { accessService: AccessService; resetTokenService: ResetTokenService; emailService: EmailService; + eventService: EventService; sessionService: SessionService; settingService: SettingService; }, ) { this.logger = getLogger('service/user-service.js'); this.store = stores.userStore; - this.eventStore = stores.eventStore; + this.eventService = services.eventService; this.accessService = services.accessService; this.resetTokenService = services.resetTokenService; this.emailService = services.emailService; @@ -208,7 +209,7 @@ class UserService { await this.store.setPasswordHash(user.id, passwordHash); } - await this.eventStore.store({ + await this.eventService.storeEvent({ type: USER_CREATED, createdBy: this.getCreatedBy(updatedBy), data: this.mapUserToData(user), @@ -257,7 +258,7 @@ class UserService { ? await this.store.update(id, payload) : preUser; - await this.eventStore.store({ + await this.eventService.storeEvent({ type: USER_UPDATED, createdBy: this.getCreatedBy(updatedBy), data: this.mapUserToData(user), @@ -274,7 +275,7 @@ class UserService { await this.store.delete(userId); - await this.eventStore.store({ + await this.eventService.storeEvent({ type: USER_DELETED, createdBy: this.getCreatedBy(updatedBy), preData: this.mapUserToData(user), diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts index 0ea5d04e20..68f68a7ca4 100644 --- a/src/lib/types/events.ts +++ b/src/lib/types/events.ts @@ -278,22 +278,15 @@ class BaseEvent implements IBaseEvent { readonly createdBy: string; - readonly tags: ITag[]; - /** * @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization */ - constructor( - type: IEventType, - createdBy: string | IUser, - tags: ITag[] = [], - ) { + constructor(type: IEventType, createdBy: string | IUser) { this.type = type; this.createdBy = typeof createdBy === 'string' ? createdBy : extractUsernameFromUser(createdBy); - this.tags = tags; } } @@ -310,13 +303,8 @@ export class FeatureStaleEvent extends BaseEvent { project: string; featureName: string; createdBy: string | IUser; - tags: ITag[]; }) { - super( - p.stale ? FEATURE_STALE_ON : FEATURE_STALE_OFF, - p.createdBy, - p.tags, - ); + super(p.stale ? FEATURE_STALE_ON : FEATURE_STALE_OFF, p.createdBy); this.project = p.project; this.featureName = p.featureName; } @@ -338,14 +326,12 @@ export class FeatureEnvironmentEvent extends BaseEvent { featureName: string; environment: string; createdBy: string | IUser; - tags: ITag[]; }) { super( p.enabled ? FEATURE_ENVIRONMENT_ENABLED : FEATURE_ENVIRONMENT_DISABLED, p.createdBy, - p.tags, ); this.project = p.project; this.featureName = p.featureName; @@ -374,9 +360,8 @@ export class StrategiesOrderChangedEvent extends BaseEvent { createdBy: string | IUser; data: StrategyIds; preData: StrategyIds; - tags: ITag[]; }) { - super(STRATEGY_ORDER_CHANGED, p.createdBy, p.tags); + super(STRATEGY_ORDER_CHANGED, p.createdBy); const { project, featureName, environment, data, preData } = p; this.project = project; this.featureName = featureName; @@ -402,11 +387,10 @@ export class FeatureVariantEvent extends BaseEvent { project: string; featureName: string; createdBy: string | IUser; - tags: ITag[]; newVariants: IVariant[]; oldVariants: IVariant[]; }) { - super(FEATURE_VARIANTS_UPDATED, p.createdBy, p.tags); + super(FEATURE_VARIANTS_UPDATED, p.createdBy); this.project = p.project; this.featureName = p.featureName; this.data = { variants: p.newVariants }; @@ -433,11 +417,10 @@ export class EnvironmentVariantEvent extends BaseEvent { environment: string; project: string; createdBy: string | IUser; - tags: ITag[]; newVariants: IVariant[]; oldVariants: IVariant[]; }) { - super(FEATURE_ENVIRONMENT_VARIANTS_UPDATED, p.createdBy, p.tags); + super(FEATURE_ENVIRONMENT_VARIANTS_UPDATED, p.createdBy); this.featureName = p.featureName; this.environment = p.environment; this.project = p.project; @@ -464,9 +447,8 @@ export class FeatureChangeProjectEvent extends BaseEvent { newProject: string; featureName: string; createdBy: string | IUser; - tags: ITag[]; }) { - super(FEATURE_PROJECT_CHANGE, p.createdBy, p.tags); + super(FEATURE_PROJECT_CHANGE, p.createdBy); const { newProject, oldProject, featureName } = p; this.project = newProject; this.featureName = featureName; @@ -489,9 +471,8 @@ export class FeatureCreatedEvent extends BaseEvent { featureName: string; createdBy: string | IUser; data: FeatureToggle; - tags: ITag[]; }) { - super(FEATURE_CREATED, p.createdBy, p.tags); + super(FEATURE_CREATED, p.createdBy); const { project, featureName, data } = p; this.project = project; this.featureName = featureName; @@ -511,9 +492,8 @@ export class FeatureArchivedEvent extends BaseEvent { project: string; featureName: string; createdBy: string | IUser; - tags: ITag[]; }) { - super(FEATURE_ARCHIVED, p.createdBy, p.tags); + super(FEATURE_ARCHIVED, p.createdBy); const { project, featureName } = p; this.project = project; this.featureName = featureName; @@ -532,9 +512,8 @@ export class FeatureRevivedEvent extends BaseEvent { project: string; featureName: string; createdBy: string | IUser; - tags: ITag[]; }) { - super(FEATURE_REVIVED, p.createdBy, p.tags); + super(FEATURE_REVIVED, p.createdBy); const { project, featureName } = p; this.project = project; this.featureName = featureName; @@ -548,6 +527,8 @@ export class FeatureDeletedEvent extends BaseEvent { readonly preData: FeatureToggle; + readonly tags: ITag[]; + /** * @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization */ @@ -558,11 +539,12 @@ export class FeatureDeletedEvent extends BaseEvent { createdBy: string | IUser; tags: ITag[]; }) { - super(FEATURE_DELETED, p.createdBy, p.tags); + super(FEATURE_DELETED, p.createdBy); const { project, featureName, preData } = p; this.project = project; this.featureName = featureName; this.preData = preData; + this.tags = p.tags; } } @@ -584,9 +566,8 @@ export class FeatureMetadataUpdateEvent extends BaseEvent { project: string; data: FeatureToggle; preData: FeatureToggle; - tags: ITag[]; }) { - super(FEATURE_METADATA_UPDATED, p.createdBy, p.tags); + super(FEATURE_METADATA_UPDATED, p.createdBy); const { project, featureName, data, preData } = p; this.project = project; this.featureName = featureName; @@ -613,9 +594,8 @@ export class FeatureStrategyAddEvent extends BaseEvent { environment: string; createdBy: string | IUser; data: IStrategyConfig; - tags: ITag[]; }) { - super(FEATURE_STRATEGY_ADD, p.createdBy, p.tags); + super(FEATURE_STRATEGY_ADD, p.createdBy); const { project, featureName, environment, data } = p; this.project = project; this.featureName = featureName; @@ -645,9 +625,8 @@ export class FeatureStrategyUpdateEvent extends BaseEvent { createdBy: string | IUser; data: IStrategyConfig; preData: IStrategyConfig; - tags: ITag[]; }) { - super(FEATURE_STRATEGY_UPDATE, p.createdBy, p.tags); + super(FEATURE_STRATEGY_UPDATE, p.createdBy); const { project, featureName, environment, data, preData } = p; this.project = project; this.featureName = featureName; @@ -675,9 +654,8 @@ export class FeatureStrategyRemoveEvent extends BaseEvent { environment: string; createdBy: string | IUser; preData: IStrategyConfig; - tags: ITag[]; }) { - super(FEATURE_STRATEGY_REMOVE, p.createdBy, p.tags); + super(FEATURE_STRATEGY_REMOVE, p.createdBy); const { project, featureName, environment, preData } = p; this.project = project; this.featureName = featureName; @@ -1075,12 +1053,8 @@ export class PotentiallyStaleOnEvent extends BaseEvent { readonly project: string; - constructor(eventData: { - featureName: string; - project: string; - tags: ITag[]; - }) { - super(FEATURE_POTENTIALLY_STALE_ON, 'unleash-system', eventData.tags); + constructor(eventData: { featureName: string; project: string }) { + super(FEATURE_POTENTIALLY_STALE_ON, 'unleash-system'); this.featureName = eventData.featureName; this.project = eventData.project; } diff --git a/src/test/e2e/api/admin/event.e2e.test.ts b/src/test/e2e/api/admin/event.e2e.test.ts index e5685321b5..2c5119584b 100644 --- a/src/test/e2e/api/admin/event.e2e.test.ts +++ b/src/test/e2e/api/admin/event.e2e.test.ts @@ -5,12 +5,12 @@ import { import dbInit, { ITestDb } from '../../helpers/database-init'; import getLogger from '../../../fixtures/no-logger'; import { FEATURE_CREATED, IBaseEvent } from '../../../../lib/types/events'; -import { IEventStore } from '../../../../lib/types/stores/event-store'; import { randomId } from '../../../../lib/util/random-id'; +import { EventService } from '../../../../lib/services'; let app: IUnleashTest; let db: ITestDb; -let eventStore: IEventStore; +let eventService: EventService; beforeAll(async () => { db = await dbInit('event_api_serial', getLogger); @@ -21,11 +21,11 @@ beforeAll(async () => { }, }, }); - eventStore = db.stores.eventStore; + eventService = new EventService(db.stores, { getLogger }); }); beforeEach(async () => { - await eventStore.deleteAll(); + await db.stores.eventStore.deleteAll(); }); afterAll(async () => { @@ -50,7 +50,7 @@ test('returns events given a name', async () => { }); test('Can filter by project', async () => { - await eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, project: 'something-else', data: { id: 'some-other-feature' }, @@ -58,7 +58,7 @@ test('Can filter by project', async () => { createdBy: 'test-user', environment: 'test', }); - await eventStore.store({ + await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { id: 'feature' }, @@ -96,7 +96,7 @@ test('can search for events', async () => { await Promise.all( events.map((event) => { - return eventStore.store(event); + return eventService.storeEvent(event); }), ); diff --git a/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts b/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts index f406615567..eec7b211aa 100644 --- a/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts +++ b/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts @@ -16,7 +16,7 @@ import { RoleName } from '../../../../lib/types/model'; import SettingService from '../../../../lib/services/setting-service'; import FakeSettingStore from '../../../fixtures/fake-setting-store'; import { GroupService } from '../../../../lib/services/group-service'; -import FakeEventStore from '../../../fixtures/fake-event-store'; +import { EventService } from '../../../../lib/services'; let app; let stores; @@ -49,7 +49,8 @@ beforeAll(async () => { db = await dbInit('reset_password_api_serial', getLogger); stores = db.stores; app = await setupApp(stores); - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); accessService = new AccessService(stores, config, groupService); const emailService = new EmailService(config.email, config.getLogger); const sessionStore = new SessionStore( @@ -61,14 +62,15 @@ beforeAll(async () => { const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); userService = new UserService(stores, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); diff --git a/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts b/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts index 4a53d120c2..6f2c6ce8ca 100644 --- a/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts +++ b/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts @@ -12,6 +12,7 @@ import { RoleName } from '../../../../lib/types/model'; import SettingService from '../../../../lib/services/setting-service'; import { GroupService } from '../../../../lib/services/group-service'; import ResetTokenService from '../../../../lib/services/reset-token-service'; +import { EventService } from '../../../../lib/services'; let app; let stores; @@ -34,18 +35,20 @@ beforeEach(async () => { db = await dbInit('simple_password_provider_api_serial', getLogger); stores = db.stores; app = await setupApp(stores); - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); const accessService = new AccessService(stores, config, groupService); const resetTokenService = new ResetTokenService(stores, config); // @ts-ignore const emailService = new EmailService(undefined, config.getLogger); const sessionService = new SessionService(stores, config); - const settingService = new SettingService(stores, config); + const settingService = new SettingService(stores, config, eventService); userService = new UserService(stores, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); diff --git a/src/test/e2e/services/access-service.e2e.test.ts b/src/test/e2e/services/access-service.e2e.test.ts index c21536fb0d..16bb654eaf 100644 --- a/src/test/e2e/services/access-service.e2e.test.ts +++ b/src/test/e2e/services/access-service.e2e.test.ts @@ -20,7 +20,7 @@ import { DEFAULT_PROJECT } from '../../../lib/types/project'; import { ALL_PROJECTS } from '../../../lib/util/constants'; import { SegmentService } from '../../../lib/services/segment-service'; import { GroupService } from '../../../lib/services/group-service'; -import { FavoritesService } from '../../../lib/services'; +import { EventService, FavoritesService } from '../../../lib/services'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; @@ -28,6 +28,7 @@ import { DependentFeaturesReadModel } from '../../../lib/features/dependent-feat let db: ITestDb; let stores: IUnleashStores; let accessService: AccessService; +let eventService: EventService; let groupService: GroupService; let featureToggleService; let favoritesService; @@ -237,7 +238,8 @@ beforeAll(async () => { // @ts-ignore experimental: { environments: { enabled: true } }, }); - groupService = new GroupService(stores, { getLogger }); + eventService = new EventService(stores, config); + groupService = new GroupService(stores, { getLogger }, eventService); accessService = new AccessService(stores, config, groupService); const roles = await accessService.getRootRoles(); editorRole = roles.find((r) => r.name === RoleName.EDITOR); @@ -261,14 +263,16 @@ beforeAll(async () => { stores, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ), accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, ); - favoritesService = new FavoritesService(stores, config); + favoritesService = new FavoritesService(stores, config, eventService); projectService = new ProjectService( stores, config, @@ -276,6 +280,7 @@ beforeAll(async () => { featureToggleService, groupService, favoritesService, + eventService, privateProjectChecker, ); diff --git a/src/test/e2e/services/addon-service.e2e.test.ts b/src/test/e2e/services/addon-service.e2e.test.ts index eea3e46ed8..20406d07f8 100644 --- a/src/test/e2e/services/addon-service.e2e.test.ts +++ b/src/test/e2e/services/addon-service.e2e.test.ts @@ -7,6 +7,7 @@ import { IUnleashStores } from '../../../lib/types'; import SimpleAddon from '../../../lib/services/addon-service-test-simple-addon'; import TagTypeService from '../../../lib/services/tag-type-service'; import { FEATURE_CREATED } from '../../../lib/types/events'; +import { EventService } from '../../../lib/services'; const addonProvider = { simple: new SimpleAddon() }; @@ -20,11 +21,13 @@ beforeAll(async () => { }); db = await dbInit('addon_service_serial', getLogger); stores = db.stores; - const tagTypeService = new TagTypeService(stores, config); + const eventService = new EventService(stores, config); + const tagTypeService = new TagTypeService(stores, config, eventService); addonService = new AddonService( stores, config, tagTypeService, + eventService, addonProvider, ); }); diff --git a/src/test/e2e/services/api-token-service.e2e.test.ts b/src/test/e2e/services/api-token-service.e2e.test.ts index 80006029fb..30468e3ce9 100644 --- a/src/test/e2e/services/api-token-service.e2e.test.ts +++ b/src/test/e2e/services/api-token-service.e2e.test.ts @@ -10,7 +10,7 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service'; import { AccessService } from '../../../lib/services/access-service'; import { SegmentService } from '../../../lib/services/segment-service'; import { GroupService } from '../../../lib/services/group-service'; -import { FavoritesService } from '../../../lib/services'; +import { EventService, FavoritesService } from '../../../lib/services'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; @@ -27,7 +27,8 @@ beforeAll(async () => { }); db = await dbInit('api_token_service_serial', getLogger); stores = db.stores; - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); const accessService = new AccessService(stores, config, groupService); const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( db.rawDatabase, @@ -47,9 +48,11 @@ beforeAll(async () => { stores, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ), accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, @@ -65,7 +68,7 @@ beforeAll(async () => { name: 'Some Name', email: 'test@getunleash.io', }); - favoritesService = new FavoritesService(stores, config); + favoritesService = new FavoritesService(stores, config, eventService); projectService = new ProjectService( stores, config, @@ -73,12 +76,13 @@ beforeAll(async () => { featureToggleService, groupService, favoritesService, + eventService, privateProjectChecker, ); await projectService.createProject(project, user); - apiTokenService = new ApiTokenService(stores, config); + apiTokenService = new ApiTokenService(stores, config, eventService); }); afterAll(async () => { diff --git a/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts b/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts index f7d026d2cc..b4b7b444af 100644 --- a/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts +++ b/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts @@ -4,6 +4,7 @@ import dbInit from '../helpers/database-init'; import { DEFAULT_ENV } from '../../../lib/util'; import { AccessService, + EventService, GroupService, SegmentService, } from '../../../lib/services'; @@ -53,8 +54,8 @@ beforeAll(async () => { ); unleashConfig = config; stores = db.stores; - - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); const accessService = new AccessService(stores, config, groupService); const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( db.rawDatabase, @@ -71,6 +72,7 @@ beforeAll(async () => { stores, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ); @@ -79,6 +81,7 @@ beforeAll(async () => { config, segmentService, accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, @@ -457,7 +460,8 @@ test('If change requests are enabled, cannot change variants without going via C { name: featureName }, 'test-user', ); - const groupService = new GroupService(stores, unleashConfig); + const eventService = new EventService(stores, unleashConfig); + const groupService = new GroupService(stores, unleashConfig, eventService); const accessService = new AccessService( stores, unleashConfig, @@ -485,6 +489,7 @@ test('If change requests are enabled, cannot change variants without going via C }, segmentService, accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, @@ -549,7 +554,8 @@ test('If CRs are protected for any environment in the project stops bulk update project.id, disabledEnv.name, ); - const groupService = new GroupService(stores, unleashConfig); + const eventService = new EventService(stores, unleashConfig); + const groupService = new GroupService(stores, unleashConfig, eventService); const accessService = new AccessService( stores, unleashConfig, @@ -577,6 +583,7 @@ test('If CRs are protected for any environment in the project stops bulk update }, segmentService, accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, diff --git a/src/test/e2e/services/group-service.e2e.test.ts b/src/test/e2e/services/group-service.e2e.test.ts index 37ab999c75..a4eab6bb62 100644 --- a/src/test/e2e/services/group-service.e2e.test.ts +++ b/src/test/e2e/services/group-service.e2e.test.ts @@ -3,10 +3,12 @@ import getLogger from '../../fixtures/no-logger'; import { createTestConfig } from '../../config/test-config'; import { GroupService } from '../../../lib/services/group-service'; import GroupStore from '../../../lib/db/group-store'; +import { EventService } from '../../../lib/services'; let stores; let db: ITestDb; +let eventService: EventService; let groupService: GroupService; let groupStore: GroupStore; let user; @@ -21,7 +23,8 @@ beforeAll(async () => { const config = createTestConfig({ getLogger, }); - groupService = new GroupService(stores, config); + eventService = new EventService(stores, config); + groupService = new GroupService(stores, config, eventService); groupStore = stores.groupStore; await stores.groupStore.create({ diff --git a/src/test/e2e/services/playground-service.test.ts b/src/test/e2e/services/playground-service.test.ts index 5d311a7e3d..dcea031d30 100644 --- a/src/test/e2e/services/playground-service.test.ts +++ b/src/test/e2e/services/playground-service.test.ts @@ -27,6 +27,7 @@ import { ISegmentService } from '../../../lib/segments/segment-service-interface import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; +import { EventService } from '../../../lib/services'; let stores: IUnleashStores; let db: ITestDb; @@ -38,7 +39,8 @@ beforeAll(async () => { const config = createTestConfig(); db = await dbInit('playground_service_serial', config.getLogger); stores = db.stores; - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); const accessService = new AccessService(stores, config, groupService); const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( db.rawDatabase, @@ -55,6 +57,7 @@ beforeAll(async () => { stores, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ); @@ -63,6 +66,7 @@ beforeAll(async () => { config, segmentService, accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, diff --git a/src/test/e2e/services/project-health-service.e2e.test.ts b/src/test/e2e/services/project-health-service.e2e.test.ts index 3d828addd5..9abefb5219 100644 --- a/src/test/e2e/services/project-health-service.e2e.test.ts +++ b/src/test/e2e/services/project-health-service.e2e.test.ts @@ -9,7 +9,7 @@ import { IUnleashStores } from '../../../lib/types'; import { IUser } from '../../../lib/server-impl'; import { SegmentService } from '../../../lib/services/segment-service'; import { GroupService } from '../../../lib/services/group-service'; -import { FavoritesService } from '../../../lib/services'; +import { EventService, FavoritesService } from '../../../lib/services'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; @@ -19,6 +19,7 @@ let db: ITestDb; let projectService; let groupService; let accessService; +let eventService: EventService; let projectHealthService; let featureToggleService; let favoritesService; @@ -32,7 +33,8 @@ beforeAll(async () => { name: 'Some Name', email: 'test@getunleash.io', }); - groupService = new GroupService(stores, config); + eventService = new EventService(stores, config); + groupService = new GroupService(stores, config, eventService); accessService = new AccessService(stores, config, groupService); const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( db.rawDatabase, @@ -52,14 +54,16 @@ beforeAll(async () => { stores, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ), accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, ); - favoritesService = new FavoritesService(stores, config); + favoritesService = new FavoritesService(stores, config, eventService); projectService = new ProjectService( stores, @@ -68,6 +72,7 @@ beforeAll(async () => { featureToggleService, groupService, favoritesService, + eventService, privateProjectChecker, ); projectHealthService = new ProjectHealthService( diff --git a/src/test/e2e/services/project-service.e2e.test.ts b/src/test/e2e/services/project-service.e2e.test.ts index f8ad773401..863d965f1f 100644 --- a/src/test/e2e/services/project-service.e2e.test.ts +++ b/src/test/e2e/services/project-service.e2e.test.ts @@ -11,7 +11,7 @@ import EnvironmentService from '../../../lib/services/environment-service'; import IncompatibleProjectError from '../../../lib/error/incompatible-project-error'; import { SegmentService } from '../../../lib/services/segment-service'; import { GroupService } from '../../../lib/services/group-service'; -import { FavoritesService } from '../../../lib/services'; +import { EventService, FavoritesService } from '../../../lib/services'; import { FeatureEnvironmentEvent } from '../../../lib/types/events'; import { addDays, subDays } from 'date-fns'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; @@ -24,6 +24,7 @@ let db: ITestDb; let projectService: ProjectService; let groupService: GroupService; let accessService: AccessService; +let eventService: EventService; let environmentService: EnvironmentService; let featureToggleService: FeatureToggleService; let favoritesService: FavoritesService; @@ -53,7 +54,8 @@ beforeAll(async () => { flags: { privateProjects: true }, }, }); - groupService = new GroupService(stores, config); + eventService = new EventService(stores, config); + groupService = new GroupService(stores, config, eventService); accessService = new AccessService(stores, config, groupService); const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( db.rawDatabase, @@ -73,15 +75,17 @@ beforeAll(async () => { stores, changeRequestAccessReadModel, config, + eventService, privateProjectChecker, ), accessService, + eventService, changeRequestAccessReadModel, privateProjectChecker, dependentFeaturesReadModel, ); - favoritesService = new FavoritesService(stores, config); + favoritesService = new FavoritesService(stores, config, eventService); environmentService = new EnvironmentService(stores, config); projectService = new ProjectService( stores, @@ -90,6 +94,7 @@ beforeAll(async () => { featureToggleService, groupService, favoritesService, + eventService, privateProjectChecker, ); }); @@ -1349,14 +1354,13 @@ test('should calculate average time to production', async () => { await Promise.all( featureToggles.map((toggle) => { - return stores.eventStore.store( + return eventService.storeEvent( new FeatureEnvironmentEvent({ enabled: true, project: project.id, featureName: toggle.name, environment: 'default', createdBy: 'Fredrik', - tags: [], }), ); }), @@ -1407,19 +1411,19 @@ test('should calculate average time to production ignoring some items', async () await updateFeature(toggle.name, { created_at: subDays(new Date(), 20), }); - await stores.eventStore.store( + await eventService.storeEvent( new FeatureEnvironmentEvent(makeEvent(toggle.name)), ); // ignore events added after first enabled await updateEventCreatedAt(addDays(new Date(), 1), toggle.name); - await stores.eventStore.store( + await eventService.storeEvent( new FeatureEnvironmentEvent(makeEvent(toggle.name)), ); // ignore toggles enabled in non-prod envs const devToggle = { name: 'dev-toggle' }; await featureToggleService.createFeatureToggle(project.id, devToggle, user); - await stores.eventStore.store( + await eventService.storeEvent( new FeatureEnvironmentEvent({ ...makeEvent(devToggle.name), environment: 'customEnv', @@ -1433,7 +1437,7 @@ test('should calculate average time to production ignoring some items', async () otherProjectToggle, user, ); - await stores.eventStore.store( + await eventService.storeEvent( new FeatureEnvironmentEvent(makeEvent(otherProjectToggle.name)), ); @@ -1444,7 +1448,7 @@ test('should calculate average time to production ignoring some items', async () nonReleaseToggle, user, ); - await stores.eventStore.store( + await eventService.storeEvent( new FeatureEnvironmentEvent(makeEvent(nonReleaseToggle.name)), ); @@ -1455,7 +1459,7 @@ test('should calculate average time to production ignoring some items', async () previouslyDeleteToggle, user, ); - await stores.eventStore.store( + await eventService.storeEvent( new FeatureEnvironmentEvent(makeEvent(previouslyDeleteToggle.name)), ); await updateEventCreatedAt( @@ -1625,14 +1629,13 @@ test('should return average time to production per toggle', async () => { await Promise.all( featureToggles.map((toggle) => { - return stores.eventStore.store( + return eventService.storeEvent( new FeatureEnvironmentEvent({ enabled: true, project: project.id, featureName: toggle.name, environment: 'default', createdBy: 'Fredrik', - tags: [], }), ); }), @@ -1704,14 +1707,13 @@ test('should return average time to production per toggle for a specific project await Promise.all( featureTogglesProject1.map((toggle) => { - return stores.eventStore.store( + return eventService.storeEvent( new FeatureEnvironmentEvent({ enabled: true, project: project1.id, featureName: toggle.name, environment: 'default', createdBy: 'Fredrik', - tags: [], }), ); }), @@ -1719,14 +1721,13 @@ test('should return average time to production per toggle for a specific project await Promise.all( featureTogglesProject2.map((toggle) => { - return stores.eventStore.store( + return eventService.storeEvent( new FeatureEnvironmentEvent({ enabled: true, project: project2.id, featureName: toggle.name, environment: 'default', createdBy: 'Fredrik', - tags: [], }), ); }), @@ -1783,14 +1784,13 @@ test('should return average time to production per toggle and include archived t await Promise.all( featureTogglesProject1.map((toggle) => { - return stores.eventStore.store( + return eventService.storeEvent( new FeatureEnvironmentEvent({ enabled: true, project: project1.id, featureName: toggle.name, environment: 'default', createdBy: 'Fredrik', - tags: [], }), ); }), diff --git a/src/test/e2e/services/reset-token-service.e2e.test.ts b/src/test/e2e/services/reset-token-service.e2e.test.ts index a10fc8c421..a6a1509137 100644 --- a/src/test/e2e/services/reset-token-service.e2e.test.ts +++ b/src/test/e2e/services/reset-token-service.e2e.test.ts @@ -12,7 +12,7 @@ import { IUser } from '../../../lib/types/user'; import SettingService from '../../../lib/services/setting-service'; import FakeSettingStore from '../../fixtures/fake-setting-store'; import { GroupService } from '../../../lib/services/group-service'; -import FakeEventStore from '../../fixtures/fake-event-store'; +import { EventService } from '../../../lib/services'; const config: IUnleashConfig = createTestConfig(); @@ -28,7 +28,8 @@ let sessionService: SessionService; beforeAll(async () => { db = await dbInit('reset_token_service_serial', getLogger); stores = db.stores; - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); accessService = new AccessService(stores, config, groupService); resetTokenService = new ResetTokenService(stores, config); sessionService = new SessionService(stores, config); @@ -36,15 +37,16 @@ beforeAll(async () => { const settingService = new SettingService( { settingStore: new FakeSettingStore(), - eventStore: new FakeEventStore(), }, config, + eventService, ); userService = new UserService(stores, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); diff --git a/src/test/e2e/services/setting-service.test.ts b/src/test/e2e/services/setting-service.test.ts index 6c6e7c503f..a72ea7deb5 100644 --- a/src/test/e2e/services/setting-service.test.ts +++ b/src/test/e2e/services/setting-service.test.ts @@ -7,6 +7,7 @@ import { SETTING_DELETED, SETTING_UPDATED, } from '../../../lib/types/events'; +import { EventService } from '../../../lib/services'; let stores: IUnleashStores; let db; @@ -16,7 +17,8 @@ beforeAll(async () => { const config = createTestConfig(); db = await dbInit('setting_service_serial', config.getLogger); stores = db.stores; - service = new SettingService(stores, config); + const eventService = new EventService(stores, config); + service = new SettingService(stores, config, eventService); }); beforeEach(async () => { await stores.eventStore.deleteAll(); diff --git a/src/test/e2e/services/state-service.e2e.test.ts b/src/test/e2e/services/state-service.e2e.test.ts index 9d74250f9c..9d8bb57ecc 100644 --- a/src/test/e2e/services/state-service.e2e.test.ts +++ b/src/test/e2e/services/state-service.e2e.test.ts @@ -3,6 +3,7 @@ import dbInit from '../helpers/database-init'; import StateService from '../../../lib/services/state-service'; import oldFormat from '../../examples/variantsexport_v3.json'; import { WeightType } from '../../../lib/types/model'; +import { EventService } from '../../../lib/services'; let stores; let db; @@ -12,7 +13,8 @@ beforeAll(async () => { const config = createTestConfig(); db = await dbInit('state_service_serial', config.getLogger); stores = db.stores; - stateService = new StateService(stores, config); + const eventService = new EventService(stores, config); + stateService = new StateService(stores, config, eventService); }); afterAll(async () => { diff --git a/src/test/e2e/services/user-service.e2e.test.ts b/src/test/e2e/services/user-service.e2e.test.ts index 3147534e32..a9da62a0b3 100644 --- a/src/test/e2e/services/user-service.e2e.test.ts +++ b/src/test/e2e/services/user-service.e2e.test.ts @@ -17,6 +17,7 @@ import { GroupService } from '../../../lib/services/group-service'; import { randomId } from '../../../lib/util/random-id'; import { BadDataError } from '../../../lib/error'; import PasswordMismatch from '../../../lib/error/password-mismatch'; +import { EventService } from '../../../lib/services'; let db; let stores; @@ -31,17 +32,19 @@ beforeAll(async () => { db = await dbInit('user_service_serial', getLogger); stores = db.stores; const config = createTestConfig(); - const groupService = new GroupService(stores, config); + const eventService = new EventService(stores, config); + const groupService = new GroupService(stores, config, eventService); const accessService = new AccessService(stores, config, groupService); const resetTokenService = new ResetTokenService(stores, config); const emailService = new EmailService(undefined, config.getLogger); sessionService = new SessionService(stores, config); - settingService = new SettingService(stores, config); + settingService = new SettingService(stores, config, eventService); userService = new UserService(stores, config, { accessService, resetTokenService, emailService, + eventService, sessionService, settingService, }); diff --git a/src/test/e2e/stores/event-store.e2e.test.ts b/src/test/e2e/stores/event-store.e2e.test.ts index 6c56769f43..ea70108f44 100644 --- a/src/test/e2e/stores/event-store.e2e.test.ts +++ b/src/test/e2e/stores/event-store.e2e.test.ts @@ -198,7 +198,6 @@ test('Should get all events of type', async () => { featureName: data.name, createdBy: 'test-user', data, - tags: [], }) : new FeatureDeletedEvent({ project: data.project,