1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-04 00:18:01 +01:00

feat: user profile returns user subscriptions (#8656)

This commit is contained in:
Mateusz Kwasniewski 2024-11-05 14:17:37 +01:00 committed by GitHub
parent 2017ab7719
commit a2a94dd011
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 64 additions and 18 deletions

View File

@ -190,10 +190,7 @@ export const createStores = (
config.flagResolver,
),
userUnsubscribeStore: new UserUnsubscribeStore(db),
userSubscriptionsReadModel: new UserSubscriptionsReadModel(
db,
eventBus,
),
userSubscriptionsReadModel: new UserSubscriptionsReadModel(db),
};
};

View File

@ -6,15 +6,18 @@ import {
createFakeEventsService,
} from '../events/createEventsService';
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 =
(config: IUnleashConfig) =>
(db: Db): UserSubscriptionsService => {
const userUnsubscribeStore = new UserUnsubscribeStore(db);
const userSubscriptionsReadModel = new UserSubscriptionsReadModel(db);
const eventService = createEventsService(db, config);
const userSubscriptionsService = new UserSubscriptionsService(
{ userUnsubscribeStore },
{ userUnsubscribeStore, userSubscriptionsReadModel },
config,
eventService,
);
@ -26,10 +29,11 @@ export const createFakeUserSubscriptionsService = (
config: IUnleashConfig,
): UserSubscriptionsService => {
const userUnsubscribeStore = new FakeUserUnsubscribeStore();
const userSubscriptionsReadModel = new FakeUserSubscriptionsReadModel();
const eventService = createFakeEventsService(config);
const userSubscriptionsService = new UserSubscriptionsService(
{ userUnsubscribeStore },
{ userUnsubscribeStore, userSubscriptionsReadModel },
config,
eventService,
);

View File

@ -2,7 +2,6 @@ import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
import getLogger from '../../../test/fixtures/no-logger';
import { UserSubscriptionsReadModel } from './user-subscriptions-read-model';
import type { IUserSubscriptionsReadModel } from './user-subscriptions-read-model-type';
import EventEmitter from 'events';
import { SUBSCRIPTION_TYPES } from './user-subscriptions-read-model-type';
import type { IUnleashStores, IUserStore } from '../../types';
import type { IUserUnsubscribeStore } from './user-unsubscribe-store-type';
@ -21,11 +20,7 @@ beforeAll(async () => {
stores = db.stores;
userStore = stores.userStore;
userUnsubscribeStore = stores.userUnsubscribeStore;
const eventBus = new EventEmitter();
userSubscriptionsReadModel = new UserSubscriptionsReadModel(
db.rawDatabase,
eventBus,
);
userSubscriptionsReadModel = new UserSubscriptionsReadModel(db.rawDatabase);
});
beforeEach(async () => {

View File

@ -1,5 +1,4 @@
import type { Db } from '../../db/db';
import type EventEmitter from 'events';
import {
SUBSCRIPTION_TYPES,
type IUserSubscriptionsReadModel,
@ -26,7 +25,7 @@ const mapRowToSubscriber = (row) =>
export class UserSubscriptionsReadModel implements IUserSubscriptionsReadModel {
private db: Db;
constructor(db: Db, eventBus: EventEmitter) {
constructor(db: Db) {
this.db = db;
}

View File

@ -6,24 +6,38 @@ import type {
UnsubscribeEntry,
} from './user-unsubscribe-store-type';
import type EventService from '../events/event-service';
import type { IUserSubscriptionsReadModel } from './user-subscriptions-read-model-type';
export class UserSubscriptionsService {
private userUnsubscribeStore: IUserUnsubscribeStore;
private userSubscriptionsReadModel: IUserSubscriptionsReadModel;
private eventService: EventService;
private logger: Logger;
constructor(
{ userUnsubscribeStore }: Pick<IUnleashStores, 'userUnsubscribeStore'>,
{
userUnsubscribeStore,
userSubscriptionsReadModel,
}: Pick<
IUnleashStores,
'userUnsubscribeStore' | 'userSubscriptionsReadModel'
>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) {
this.userUnsubscribeStore = userUnsubscribeStore;
this.userSubscriptionsReadModel = userSubscriptionsReadModel;
this.eventService = eventService;
this.logger = getLogger('services/user-subscription-service.ts');
}
async getUserSubscriptions(userId: number) {
return this.userSubscriptionsReadModel.getUserSubscriptions(userId);
}
async subscribe(
userId: number,
subscription: string,

View File

@ -9,6 +9,7 @@ test('profileSchema', () => {
name: 'Admin',
},
projects: ['default', 'secretproject'],
subscriptions: ['productivity-report'],
features: [
{ name: 'firstFeature', project: 'default' },
{ name: 'secondFeature', project: 'secretproject' },

View File

@ -7,7 +7,7 @@ export const profileSchema = {
type: 'object',
additionalProperties: false,
description: 'User profile overview',
required: ['rootRole', 'projects', 'features'],
required: ['rootRole', 'projects', 'features', 'subscriptions'],
properties: {
rootRole: {
$ref: '#/components/schemas/roleSchema',
@ -20,6 +20,14 @@ export const profileSchema = {
},
example: ['my-projectA', 'my-projectB'],
},
subscriptions: {
description: 'Which email subscriptions this user is subscribed to',
type: 'array',
items: {
type: 'string',
},
example: ['productivity-report'],
},
features: {
description: 'Deprecated, always returns empty array',
type: 'array',

View File

@ -53,6 +53,24 @@ test('should return current user', async () => {
});
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 () => {
const { request, base, userStore } = await getSetup();
await request

View File

@ -32,6 +32,7 @@ import {
type RolesSchema,
} from '../../../openapi/spec/roles-schema';
import type { IFlagResolver } from '../../../types';
import type { UserSubscriptionsService } from '../../../features/user-subscriptions/user-subscriptions-service';
class UserController extends Controller {
private accessService: AccessService;
@ -48,6 +49,8 @@ class UserController extends Controller {
private flagResolver: IFlagResolver;
private userSubscriptionsService: UserSubscriptionsService;
constructor(
config: IUnleashConfig,
{
@ -57,6 +60,7 @@ class UserController extends Controller {
userSplashService,
openApiService,
projectService,
transactionalUserSubscriptionsService,
}: Pick<
IUnleashServices,
| 'accessService'
@ -65,6 +69,7 @@ class UserController extends Controller {
| 'userSplashService'
| 'openApiService'
| 'projectService'
| 'transactionalUserSubscriptionsService'
>,
) {
super(config);
@ -74,6 +79,7 @@ class UserController extends Controller {
this.userSplashService = userSplashService;
this.openApiService = openApiService;
this.projectService = projectService;
this.userSubscriptionsService = transactionalUserSubscriptionsService;
this.flagResolver = config.flagResolver;
this.route({
@ -237,12 +243,16 @@ class UserController extends Controller {
): Promise<void> {
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 = {
projects,
rootRole,
subscriptions,
features: [],
};