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:
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