mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: user profile returns user subscriptions (#8656)
This commit is contained in:
		
							parent
							
								
									2017ab7719
								
							
						
					
					
						commit
						a2a94dd011
					
				| @ -190,10 +190,7 @@ export const createStores = ( | |||||||
|             config.flagResolver, |             config.flagResolver, | ||||||
|         ), |         ), | ||||||
|         userUnsubscribeStore: new UserUnsubscribeStore(db), |         userUnsubscribeStore: new UserUnsubscribeStore(db), | ||||||
|         userSubscriptionsReadModel: new UserSubscriptionsReadModel( |         userSubscriptionsReadModel: new UserSubscriptionsReadModel(db), | ||||||
|             db, |  | ||||||
|             eventBus, |  | ||||||
|         ), |  | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,15 +6,18 @@ import { | |||||||
|     createFakeEventsService, |     createFakeEventsService, | ||||||
| } from '../events/createEventsService'; | } from '../events/createEventsService'; | ||||||
| import { FakeUserUnsubscribeStore } from './fake-user-unsubscribe-store'; | import { FakeUserUnsubscribeStore } from './fake-user-unsubscribe-store'; | ||||||
|  | import { UserSubscriptionsReadModel } from './user-subscriptions-read-model'; | ||||||
|  | import { FakeUserSubscriptionsReadModel } from './fake-user-subscriptions-read-model'; | ||||||
| 
 | 
 | ||||||
| export const createUserSubscriptionsService = | export const createUserSubscriptionsService = | ||||||
|     (config: IUnleashConfig) => |     (config: IUnleashConfig) => | ||||||
|     (db: Db): UserSubscriptionsService => { |     (db: Db): UserSubscriptionsService => { | ||||||
|         const userUnsubscribeStore = new UserUnsubscribeStore(db); |         const userUnsubscribeStore = new UserUnsubscribeStore(db); | ||||||
|  |         const userSubscriptionsReadModel = new UserSubscriptionsReadModel(db); | ||||||
|         const eventService = createEventsService(db, config); |         const eventService = createEventsService(db, config); | ||||||
| 
 | 
 | ||||||
|         const userSubscriptionsService = new UserSubscriptionsService( |         const userSubscriptionsService = new UserSubscriptionsService( | ||||||
|             { userUnsubscribeStore }, |             { userUnsubscribeStore, userSubscriptionsReadModel }, | ||||||
|             config, |             config, | ||||||
|             eventService, |             eventService, | ||||||
|         ); |         ); | ||||||
| @ -26,10 +29,11 @@ export const createFakeUserSubscriptionsService = ( | |||||||
|     config: IUnleashConfig, |     config: IUnleashConfig, | ||||||
| ): UserSubscriptionsService => { | ): UserSubscriptionsService => { | ||||||
|     const userUnsubscribeStore = new FakeUserUnsubscribeStore(); |     const userUnsubscribeStore = new FakeUserUnsubscribeStore(); | ||||||
|  |     const userSubscriptionsReadModel = new FakeUserSubscriptionsReadModel(); | ||||||
|     const eventService = createFakeEventsService(config); |     const eventService = createFakeEventsService(config); | ||||||
| 
 | 
 | ||||||
|     const userSubscriptionsService = new UserSubscriptionsService( |     const userSubscriptionsService = new UserSubscriptionsService( | ||||||
|         { userUnsubscribeStore }, |         { userUnsubscribeStore, userSubscriptionsReadModel }, | ||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init'; | |||||||
| import getLogger from '../../../test/fixtures/no-logger'; | import getLogger from '../../../test/fixtures/no-logger'; | ||||||
| import { UserSubscriptionsReadModel } from './user-subscriptions-read-model'; | import { UserSubscriptionsReadModel } from './user-subscriptions-read-model'; | ||||||
| import type { IUserSubscriptionsReadModel } from './user-subscriptions-read-model-type'; | import type { IUserSubscriptionsReadModel } from './user-subscriptions-read-model-type'; | ||||||
| import EventEmitter from 'events'; |  | ||||||
| import { SUBSCRIPTION_TYPES } from './user-subscriptions-read-model-type'; | import { SUBSCRIPTION_TYPES } from './user-subscriptions-read-model-type'; | ||||||
| import type { IUnleashStores, IUserStore } from '../../types'; | import type { IUnleashStores, IUserStore } from '../../types'; | ||||||
| import type { IUserUnsubscribeStore } from './user-unsubscribe-store-type'; | import type { IUserUnsubscribeStore } from './user-unsubscribe-store-type'; | ||||||
| @ -21,11 +20,7 @@ beforeAll(async () => { | |||||||
|     stores = db.stores; |     stores = db.stores; | ||||||
|     userStore = stores.userStore; |     userStore = stores.userStore; | ||||||
|     userUnsubscribeStore = stores.userUnsubscribeStore; |     userUnsubscribeStore = stores.userUnsubscribeStore; | ||||||
|     const eventBus = new EventEmitter(); |     userSubscriptionsReadModel = new UserSubscriptionsReadModel(db.rawDatabase); | ||||||
|     userSubscriptionsReadModel = new UserSubscriptionsReadModel( |  | ||||||
|         db.rawDatabase, |  | ||||||
|         eventBus, |  | ||||||
|     ); |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| beforeEach(async () => { | beforeEach(async () => { | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import type { Db } from '../../db/db'; | import type { Db } from '../../db/db'; | ||||||
| import type EventEmitter from 'events'; |  | ||||||
| import { | import { | ||||||
|     SUBSCRIPTION_TYPES, |     SUBSCRIPTION_TYPES, | ||||||
|     type IUserSubscriptionsReadModel, |     type IUserSubscriptionsReadModel, | ||||||
| @ -26,7 +25,7 @@ const mapRowToSubscriber = (row) => | |||||||
| export class UserSubscriptionsReadModel implements IUserSubscriptionsReadModel { | export class UserSubscriptionsReadModel implements IUserSubscriptionsReadModel { | ||||||
|     private db: Db; |     private db: Db; | ||||||
| 
 | 
 | ||||||
|     constructor(db: Db, eventBus: EventEmitter) { |     constructor(db: Db) { | ||||||
|         this.db = db; |         this.db = db; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,24 +6,38 @@ import type { | |||||||
|     UnsubscribeEntry, |     UnsubscribeEntry, | ||||||
| } from './user-unsubscribe-store-type'; | } from './user-unsubscribe-store-type'; | ||||||
| import type EventService from '../events/event-service'; | import type EventService from '../events/event-service'; | ||||||
|  | import type { IUserSubscriptionsReadModel } from './user-subscriptions-read-model-type'; | ||||||
| 
 | 
 | ||||||
| export class UserSubscriptionsService { | export class UserSubscriptionsService { | ||||||
|     private userUnsubscribeStore: IUserUnsubscribeStore; |     private userUnsubscribeStore: IUserUnsubscribeStore; | ||||||
| 
 | 
 | ||||||
|  |     private userSubscriptionsReadModel: IUserSubscriptionsReadModel; | ||||||
|  | 
 | ||||||
|     private eventService: EventService; |     private eventService: EventService; | ||||||
| 
 | 
 | ||||||
|     private logger: Logger; |     private logger: Logger; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         { userUnsubscribeStore }: Pick<IUnleashStores, 'userUnsubscribeStore'>, |         { | ||||||
|  |             userUnsubscribeStore, | ||||||
|  |             userSubscriptionsReadModel, | ||||||
|  |         }: Pick< | ||||||
|  |             IUnleashStores, | ||||||
|  |             'userUnsubscribeStore' | 'userSubscriptionsReadModel' | ||||||
|  |         >, | ||||||
|         { getLogger }: Pick<IUnleashConfig, 'getLogger'>, |         { getLogger }: Pick<IUnleashConfig, 'getLogger'>, | ||||||
|         eventService: EventService, |         eventService: EventService, | ||||||
|     ) { |     ) { | ||||||
|         this.userUnsubscribeStore = userUnsubscribeStore; |         this.userUnsubscribeStore = userUnsubscribeStore; | ||||||
|  |         this.userSubscriptionsReadModel = userSubscriptionsReadModel; | ||||||
|         this.eventService = eventService; |         this.eventService = eventService; | ||||||
|         this.logger = getLogger('services/user-subscription-service.ts'); |         this.logger = getLogger('services/user-subscription-service.ts'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async getUserSubscriptions(userId: number) { | ||||||
|  |         return this.userSubscriptionsReadModel.getUserSubscriptions(userId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async subscribe( |     async subscribe( | ||||||
|         userId: number, |         userId: number, | ||||||
|         subscription: string, |         subscription: string, | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ test('profileSchema', () => { | |||||||
|             name: 'Admin', |             name: 'Admin', | ||||||
|         }, |         }, | ||||||
|         projects: ['default', 'secretproject'], |         projects: ['default', 'secretproject'], | ||||||
|  |         subscriptions: ['productivity-report'], | ||||||
|         features: [ |         features: [ | ||||||
|             { name: 'firstFeature', project: 'default' }, |             { name: 'firstFeature', project: 'default' }, | ||||||
|             { name: 'secondFeature', project: 'secretproject' }, |             { name: 'secondFeature', project: 'secretproject' }, | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ export const profileSchema = { | |||||||
|     type: 'object', |     type: 'object', | ||||||
|     additionalProperties: false, |     additionalProperties: false, | ||||||
|     description: 'User profile overview', |     description: 'User profile overview', | ||||||
|     required: ['rootRole', 'projects', 'features'], |     required: ['rootRole', 'projects', 'features', 'subscriptions'], | ||||||
|     properties: { |     properties: { | ||||||
|         rootRole: { |         rootRole: { | ||||||
|             $ref: '#/components/schemas/roleSchema', |             $ref: '#/components/schemas/roleSchema', | ||||||
| @ -20,6 +20,14 @@ export const profileSchema = { | |||||||
|             }, |             }, | ||||||
|             example: ['my-projectA', 'my-projectB'], |             example: ['my-projectA', 'my-projectB'], | ||||||
|         }, |         }, | ||||||
|  |         subscriptions: { | ||||||
|  |             description: 'Which email subscriptions this user is subscribed to', | ||||||
|  |             type: 'array', | ||||||
|  |             items: { | ||||||
|  |                 type: 'string', | ||||||
|  |             }, | ||||||
|  |             example: ['productivity-report'], | ||||||
|  |         }, | ||||||
|         features: { |         features: { | ||||||
|             description: 'Deprecated, always returns empty array', |             description: 'Deprecated, always returns empty array', | ||||||
|             type: 'array', |             type: 'array', | ||||||
|  | |||||||
| @ -53,6 +53,24 @@ test('should return current user', async () => { | |||||||
| }); | }); | ||||||
| const owaspPassword = 't7GTx&$Y9pcsnxRv6'; | const owaspPassword = 't7GTx&$Y9pcsnxRv6'; | ||||||
| 
 | 
 | ||||||
|  | test('should return current profile', async () => { | ||||||
|  |     expect.assertions(1); | ||||||
|  |     const { request, base } = await getSetup(); | ||||||
|  | 
 | ||||||
|  |     return request | ||||||
|  |         .get(`${base}/api/admin/user/profile`) | ||||||
|  |         .expect(200) | ||||||
|  |         .expect('Content-Type', /json/) | ||||||
|  |         .expect((res) => { | ||||||
|  |             expect(res.body).toMatchObject({ | ||||||
|  |                 projects: [], | ||||||
|  |                 rootRole: { id: -1, name: 'Viewer', type: 'root' }, | ||||||
|  |                 subscriptions: ['productivity-report'], | ||||||
|  |                 features: [], | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| test('should allow user to change password', async () => { | test('should allow user to change password', async () => { | ||||||
|     const { request, base, userStore } = await getSetup(); |     const { request, base, userStore } = await getSetup(); | ||||||
|     await request |     await request | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ import { | |||||||
|     type RolesSchema, |     type RolesSchema, | ||||||
| } from '../../../openapi/spec/roles-schema'; | } from '../../../openapi/spec/roles-schema'; | ||||||
| import type { IFlagResolver } from '../../../types'; | import type { IFlagResolver } from '../../../types'; | ||||||
|  | import type { UserSubscriptionsService } from '../../../features/user-subscriptions/user-subscriptions-service'; | ||||||
| 
 | 
 | ||||||
| class UserController extends Controller { | class UserController extends Controller { | ||||||
|     private accessService: AccessService; |     private accessService: AccessService; | ||||||
| @ -48,6 +49,8 @@ class UserController extends Controller { | |||||||
| 
 | 
 | ||||||
|     private flagResolver: IFlagResolver; |     private flagResolver: IFlagResolver; | ||||||
| 
 | 
 | ||||||
|  |     private userSubscriptionsService: UserSubscriptionsService; | ||||||
|  | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         config: IUnleashConfig, |         config: IUnleashConfig, | ||||||
|         { |         { | ||||||
| @ -57,6 +60,7 @@ class UserController extends Controller { | |||||||
|             userSplashService, |             userSplashService, | ||||||
|             openApiService, |             openApiService, | ||||||
|             projectService, |             projectService, | ||||||
|  |             transactionalUserSubscriptionsService, | ||||||
|         }: Pick< |         }: Pick< | ||||||
|             IUnleashServices, |             IUnleashServices, | ||||||
|             | 'accessService' |             | 'accessService' | ||||||
| @ -65,6 +69,7 @@ class UserController extends Controller { | |||||||
|             | 'userSplashService' |             | 'userSplashService' | ||||||
|             | 'openApiService' |             | 'openApiService' | ||||||
|             | 'projectService' |             | 'projectService' | ||||||
|  |             | 'transactionalUserSubscriptionsService' | ||||||
|         >, |         >, | ||||||
|     ) { |     ) { | ||||||
|         super(config); |         super(config); | ||||||
| @ -74,6 +79,7 @@ class UserController extends Controller { | |||||||
|         this.userSplashService = userSplashService; |         this.userSplashService = userSplashService; | ||||||
|         this.openApiService = openApiService; |         this.openApiService = openApiService; | ||||||
|         this.projectService = projectService; |         this.projectService = projectService; | ||||||
|  |         this.userSubscriptionsService = transactionalUserSubscriptionsService; | ||||||
|         this.flagResolver = config.flagResolver; |         this.flagResolver = config.flagResolver; | ||||||
| 
 | 
 | ||||||
|         this.route({ |         this.route({ | ||||||
| @ -237,12 +243,16 @@ class UserController extends Controller { | |||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         const { user } = req; |         const { user } = req; | ||||||
| 
 | 
 | ||||||
|         const projects = await this.projectService.getProjectsByUser(user.id); |         const [projects, rootRole, subscriptions] = await Promise.all([ | ||||||
|  |             this.projectService.getProjectsByUser(user.id), | ||||||
|  |             this.accessService.getRootRoleForUser(user.id), | ||||||
|  |             this.userSubscriptionsService.getUserSubscriptions(user.id), | ||||||
|  |         ]); | ||||||
| 
 | 
 | ||||||
|         const rootRole = await this.accessService.getRootRoleForUser(user.id); |  | ||||||
|         const responseData: ProfileSchema = { |         const responseData: ProfileSchema = { | ||||||
|             projects, |             projects, | ||||||
|             rootRole, |             rootRole, | ||||||
|  |             subscriptions, | ||||||
|             features: [], |             features: [], | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user