mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: add Unleash admins to API payload (#8299)
Adds Unleash admins to the personal dashboard payload. Uses the access store (and a new method) to fetch admins and maps it to a new `MinimalUser` type. We already have a `User` class, but it contains a lot of information we don't care about here, such as `isAPI`, SCIM data etc. In the UI, admins will be shown to users who are not part of any projects. This is the default state for new viewer users, and can also happen for editors if you archive the default project, for instance. Tests in a follow-up PR
This commit is contained in:
parent
751c2fa902
commit
a4ea46dab6
@ -4,7 +4,7 @@ import User from '../types/user';
|
||||
import NotFoundError from '../error/notfound-error';
|
||||
import type { IUserLookup } from '../types/stores/user-store';
|
||||
import type { IAdminCount } from '../types/stores/account-store';
|
||||
import type { IAccountStore } from '../types';
|
||||
import type { IAccountStore, MinimalUser } from '../types';
|
||||
import type { Db } from './db';
|
||||
|
||||
const TABLE = 'users';
|
||||
@ -198,4 +198,34 @@ export class AccountStore implements IAccountStore {
|
||||
service: adminCount[0].service,
|
||||
};
|
||||
}
|
||||
|
||||
async getAdmins(): Promise<MinimalUser[]> {
|
||||
const rowToAdminUser = (row) => {
|
||||
return {
|
||||
id: row.id,
|
||||
name: emptify(row.name),
|
||||
username: emptify(row.username),
|
||||
email: emptify(row.email),
|
||||
imageUrl: emptify(row.image_url),
|
||||
};
|
||||
};
|
||||
|
||||
const admins = await this.activeAccounts()
|
||||
.join('role_user as ru', 'users.id', 'ru.user_id')
|
||||
.where(
|
||||
'ru.role_id',
|
||||
'=',
|
||||
this.db.raw('(SELECT id FROM roles WHERE name = ?)', ['Admin']),
|
||||
)
|
||||
.andWhereNot('users.is_service', true)
|
||||
.select(
|
||||
'users.id',
|
||||
'users.name',
|
||||
'users.username',
|
||||
'users.email',
|
||||
'users.image_url',
|
||||
);
|
||||
|
||||
return admins.map(rowToAdminUser);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import { FeatureEventFormatterMd } from '../../addons/feature-event-formatter-md
|
||||
import FakeEventStore from '../../../test/fixtures/fake-event-store';
|
||||
import { FakePrivateProjectChecker } from '../private-project/fakePrivateProjectChecker';
|
||||
import { PrivateProjectChecker } from '../private-project/privateProjectChecker';
|
||||
import { AccountStore } from '../../db/account-store';
|
||||
import { FakeAccountStore } from '../../../test/fixtures/fake-account-store';
|
||||
|
||||
export const createPersonalDashboardService = (
|
||||
db: Db,
|
||||
@ -28,6 +30,7 @@ export const createPersonalDashboardService = (
|
||||
formatStyle: 'markdown',
|
||||
}),
|
||||
new PrivateProjectChecker(stores, config),
|
||||
new AccountStore(db, config.getLogger),
|
||||
);
|
||||
};
|
||||
|
||||
@ -42,5 +45,6 @@ export const createFakePersonalDashboardService = (config: IUnleashConfig) => {
|
||||
formatStyle: 'markdown',
|
||||
}),
|
||||
new FakePrivateProjectChecker(),
|
||||
new FakeAccountStore(),
|
||||
);
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { IUser } from '../../server-impl';
|
||||
import type {
|
||||
BasePersonalProject,
|
||||
IPersonalDashboardReadModel,
|
||||
@ -14,4 +15,8 @@ export class FakePersonalDashboardReadModel
|
||||
async getPersonalProjects(userId: number): Promise<BasePersonalProject[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getAdmins(): Promise<IUser[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -83,17 +83,18 @@ export default class PersonalDashboardController extends Controller {
|
||||
): Promise<void> {
|
||||
const user = req.user;
|
||||
|
||||
const [flags, projects, projectOwners] = await Promise.all([
|
||||
const [flags, projects, projectOwners, admins] = await Promise.all([
|
||||
this.personalDashboardService.getPersonalFeatures(user.id),
|
||||
this.personalDashboardService.getPersonalProjects(user.id),
|
||||
this.personalDashboardService.getProjectOwners(user.id),
|
||||
this.personalDashboardService.getAdmins(),
|
||||
]);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
personalDashboardSchema.$id,
|
||||
{ projects, flags, projectOwners },
|
||||
{ projects, flags, projectOwners, admins },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import type {
|
||||
} from './personal-dashboard-read-model-type';
|
||||
import type { IProjectReadModel } from '../project/project-read-model-type';
|
||||
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
|
||||
import type { IEventStore } from '../../types';
|
||||
import type { IAccountStore, IEventStore, MinimalUser } from '../../types';
|
||||
import type { FeatureEventFormatter } from '../../addons/feature-event-formatter-md';
|
||||
import { generateImageUrl } from '../../util';
|
||||
|
||||
@ -35,6 +35,8 @@ export class PersonalDashboardService {
|
||||
|
||||
private featureEventFormatter: FeatureEventFormatter;
|
||||
|
||||
private accountStore: IAccountStore;
|
||||
|
||||
constructor(
|
||||
personalDashboardReadModel: IPersonalDashboardReadModel,
|
||||
projectOwnersReadModel: IProjectOwnersReadModel,
|
||||
@ -42,6 +44,7 @@ export class PersonalDashboardService {
|
||||
eventStore: IEventStore,
|
||||
featureEventFormatter: FeatureEventFormatter,
|
||||
privateProjectChecker: IPrivateProjectChecker,
|
||||
accountStore: IAccountStore,
|
||||
) {
|
||||
this.personalDashboardReadModel = personalDashboardReadModel;
|
||||
this.projectOwnersReadModel = projectOwnersReadModel;
|
||||
@ -49,6 +52,7 @@ export class PersonalDashboardService {
|
||||
this.eventStore = eventStore;
|
||||
this.featureEventFormatter = featureEventFormatter;
|
||||
this.privateProjectChecker = privateProjectChecker;
|
||||
this.accountStore = accountStore;
|
||||
}
|
||||
|
||||
getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
|
||||
@ -105,4 +109,8 @@ export class PersonalDashboardService {
|
||||
|
||||
return { latestEvents: formattedEvents };
|
||||
}
|
||||
|
||||
async getAdmins(): Promise<MinimalUser[]> {
|
||||
return this.accountStore.getAdmins();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,41 @@ export const personalDashboardSchema = {
|
||||
additionalProperties: false,
|
||||
required: ['projects', 'flags'],
|
||||
properties: {
|
||||
admins: {
|
||||
type: 'array',
|
||||
description: 'Users with the admin role in Unleash.',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['id'],
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'The user ID.',
|
||||
example: 1,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: "The user's name.",
|
||||
example: 'Ash Ketchum',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
description: "The user's username.",
|
||||
example: 'pokémaster13',
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
example: 'https://example.com/peek-at-you.jpg',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
example: 'user@example.com',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
projectOwners: {
|
||||
type: 'array',
|
||||
description:
|
||||
@ -18,19 +53,25 @@ export const personalDashboardSchema = {
|
||||
ownerType: {
|
||||
type: 'string',
|
||||
enum: ['user'],
|
||||
description:
|
||||
'The type of the owner; will always be `user`.',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
example: 'User Name',
|
||||
description:
|
||||
"The name displayed for the user. Can be the user's name, username, or email, depending on what they have provided.",
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: "The URL of the user's profile image.",
|
||||
example: 'https://example.com/image.jpg',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: "The user's email address.",
|
||||
example: 'user@example.com',
|
||||
},
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { IUser } from '../user';
|
||||
import type { IUser, MinimalUser } from '../user';
|
||||
import type { Store } from './store';
|
||||
|
||||
export interface IUserLookup {
|
||||
@ -22,4 +22,5 @@ export interface IAccountStore extends Store<IUser, number> {
|
||||
getAccountByPersonalAccessToken(secret: string): Promise<IUser>;
|
||||
markSeenAt(secrets: string[]): Promise<void>;
|
||||
getAdminCount(): Promise<IAdminCount>;
|
||||
getAdmins(): Promise<MinimalUser[]>;
|
||||
}
|
||||
|
@ -33,6 +33,11 @@ export interface IUser {
|
||||
scimId?: string;
|
||||
}
|
||||
|
||||
export type MinimalUser = Pick<
|
||||
IUser,
|
||||
'id' | 'name' | 'username' | 'email' | 'imageUrl'
|
||||
>;
|
||||
|
||||
export interface IProjectUser extends IUser {
|
||||
addedAt: Date;
|
||||
}
|
||||
|
5
src/test/fixtures/fake-account-store.ts
vendored
5
src/test/fixtures/fake-account-store.ts
vendored
@ -15,7 +15,6 @@ export class FakeAccountStore implements IAccountStore {
|
||||
this.idSeq = 1;
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
async hasAccount({
|
||||
id,
|
||||
username,
|
||||
@ -98,4 +97,8 @@ export class FakeAccountStore implements IAccountStore {
|
||||
async getAdminCount(): Promise<IAdminCount> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
async getAdmins(): Promise<IUser[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user