From 6a8a75ce71f0921d3e14c207f2f84c0618b78a5e Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Tue, 5 Nov 2024 10:50:31 +0100 Subject: [PATCH] feat: composition root for user subscriptions (#8649) Co-Authored-By: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> --- src/lib/db/index.ts | 2 +- .../createUserSubscriptionsService.ts | 36 ++++++++++ .../user-subscriptions-read-model.ts | 6 +- .../user-subscriptions-service.e2e.test.ts | 67 +++++++++++++++++++ .../user-unsubscribe-store.ts | 17 +---- 5 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 src/lib/features/user-subscriptions/createUserSubscriptionsService.ts create mode 100644 src/lib/features/user-subscriptions/user-subscriptions-service.e2e.test.ts diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index f52d7b62c500..4785a3fbbbaa 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -189,7 +189,7 @@ export const createStores = ( eventBus, config.flagResolver, ), - userUnsubscribeStore: new UserUnsubscribeStore(db, getLogger), + userUnsubscribeStore: new UserUnsubscribeStore(db), userSubscriptionsReadModel: new UserSubscriptionsReadModel( db, eventBus, diff --git a/src/lib/features/user-subscriptions/createUserSubscriptionsService.ts b/src/lib/features/user-subscriptions/createUserSubscriptionsService.ts new file mode 100644 index 000000000000..b7dca58b6c40 --- /dev/null +++ b/src/lib/features/user-subscriptions/createUserSubscriptionsService.ts @@ -0,0 +1,36 @@ +import type { Db, IUnleashConfig } from '../../server-impl'; +import UserSubscriptionService from './user-subscriptions-service'; +import { UserUnsubscribeStore } from './user-unsubscribe-store'; +import { + createEventsService, + createFakeEventsService, +} from '../events/createEventsService'; +import { FakeUserUnsubscribeStore } from './fake-user-unsubscribe-store'; + +export const createUserSubscriptionsService = + (config: IUnleashConfig) => + (db: Db): UserSubscriptionService => { + const userUnsubscribeStore = new UserUnsubscribeStore(db); + const eventService = createEventsService(db, config); + + const userSubscriptionsService = new UserSubscriptionService( + { userUnsubscribeStore }, + config, + eventService, + ); + + return userSubscriptionsService; + }; + +export const createFakeUserSubscriptionsService = (config: IUnleashConfig) => { + const userUnsubscribeStore = new FakeUserUnsubscribeStore(); + const eventService = createFakeEventsService(config); + + const userSubscriptionsService = new UserSubscriptionService( + { userUnsubscribeStore }, + config, + eventService, + ); + + return userSubscriptionsService; +}; diff --git a/src/lib/features/user-subscriptions/user-subscriptions-read-model.ts b/src/lib/features/user-subscriptions/user-subscriptions-read-model.ts index 746457ca83a4..a6f80363a02d 100644 --- a/src/lib/features/user-subscriptions/user-subscriptions-read-model.ts +++ b/src/lib/features/user-subscriptions/user-subscriptions-read-model.ts @@ -45,10 +45,14 @@ export class UserSubscriptionsReadModel implements IUserSubscriptionsReadModel { } async getUserSubscriptions(userId: number) { - const unsubscriptions = await this.db(UNSUBSCRIPTION_TABLE) + const unsubscriptionsList = await this.db(UNSUBSCRIPTION_TABLE) .select('subscription') .where('user_id', userId); + const unsubscriptions: string[] = unsubscriptionsList.map( + (item) => item.subscription, + ); + return SUBSCRIPTION_TYPES.filter( (subscription) => !unsubscriptions.includes(subscription), ); diff --git a/src/lib/features/user-subscriptions/user-subscriptions-service.e2e.test.ts b/src/lib/features/user-subscriptions/user-subscriptions-service.e2e.test.ts new file mode 100644 index 000000000000..faf9bfff20db --- /dev/null +++ b/src/lib/features/user-subscriptions/user-subscriptions-service.e2e.test.ts @@ -0,0 +1,67 @@ +import { + type IUnleashConfig, + type IUnleashStores, + type IUser, + TEST_AUDIT_USER, +} from '../../types'; +import type UserSubscriptionService from './user-subscriptions-service'; +import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init'; +import { createTestConfig } from '../../../test/config/test-config'; +import getLogger from '../../../test/fixtures/no-logger'; +import { createUserSubscriptionsService } from './createUserSubscriptionsService'; +import type { IUserSubscriptionsReadModel } from './user-subscriptions-read-model-type'; + +let stores: IUnleashStores; +let db: ITestDb; +let userSubscriptionService: UserSubscriptionService; +let userSubscriptionsReadModel: IUserSubscriptionsReadModel; +let config: IUnleashConfig; +let user: IUser; + +beforeAll(async () => { + db = await dbInit('user_subscriptions', getLogger); + stores = db.stores; + config = createTestConfig({}); + + userSubscriptionService = createUserSubscriptionsService(config)( + db.rawDatabase, + ); + userSubscriptionsReadModel = db.stores.userSubscriptionsReadModel; + + user = await stores.userStore.insert({ + email: 'test@getunleash.io', + name: 'Sample Name', + }); +}); + +afterAll(async () => { + await db.destroy(); +}); + +test('Subscribe and unsubscribe', async () => { + const subscribers = await userSubscriptionsReadModel.getSubscribedUsers( + 'productivity-report', + ); + expect(subscribers).toMatchObject([ + { email: 'test@getunleash.io', name: 'Sample Name' }, + ]); + + const userSubscriptions = + await userSubscriptionsReadModel.getUserSubscriptions(user.id); + expect(userSubscriptions).toMatchObject(['productivity-report']); + + await userSubscriptionService.unsubscribe( + user.id, + 'productivity-report', + TEST_AUDIT_USER, + ); + + const noSubscribers = await userSubscriptionsReadModel.getSubscribedUsers( + 'productivity-report', + ); + expect(noSubscribers).toMatchObject([]); + + const noUserSubscriptions = + await userSubscriptionsReadModel.getUserSubscriptions(user.id); + expect(noUserSubscriptions).toMatchObject([]); +}); diff --git a/src/lib/features/user-subscriptions/user-unsubscribe-store.ts b/src/lib/features/user-subscriptions/user-unsubscribe-store.ts index 6562c06ba3b2..53c90b070c7e 100644 --- a/src/lib/features/user-subscriptions/user-unsubscribe-store.ts +++ b/src/lib/features/user-subscriptions/user-unsubscribe-store.ts @@ -1,9 +1,5 @@ -import type { Logger, LogProvider } from '../../logger'; import type { Db } from '../../db/db'; -import type { - UnsubscribeEntry, - IUserUnsubscribeStore, -} from './user-unsubscribe-store-type'; +import type { IUserUnsubscribeStore } from './user-unsubscribe-store-type'; const COLUMNS = ['user_id', 'subscription', 'created_at']; export const TABLE = 'user_unsubscription'; @@ -14,20 +10,11 @@ interface IUserUnsubscribeTable { created_at?: Date; } -const rowToField = (row: IUserUnsubscribeTable): UnsubscribeEntry => ({ - userId: row.user_id, - subscription: row.subscription, - createdAt: row.created_at, -}); - export class UserUnsubscribeStore implements IUserUnsubscribeStore { private db: Db; - private logger: Logger; - - constructor(db: Db, getLogger: LogProvider) { + constructor(db: Db) { this.db = db; - this.logger = getLogger('user-unsubscribe-store.ts'); } async insert({ userId, subscription }) {