mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
Backend for profile page (#2114)
* First version of profile * Fix tests * Fix typings * Replace where to andwhere to be more clear
This commit is contained in:
parent
0302b3d2e3
commit
ec7e256140
@ -288,7 +288,31 @@ class ProjectStore implements IProjectStore {
|
||||
return members;
|
||||
}
|
||||
|
||||
async getMembersCountByProject(projectId?: string): Promise<number> {
|
||||
async getProjectsByUser(userId: number): Promise<string[]> {
|
||||
const members = await this.db.from((db) => {
|
||||
db.select('project')
|
||||
.from('role_user')
|
||||
.leftJoin('roles', 'role_user.role_id', 'roles.id')
|
||||
.where('type', 'root')
|
||||
.andWhere('name', 'Editor')
|
||||
.andWhere('user_id', userId)
|
||||
.union((queryBuilder) => {
|
||||
queryBuilder
|
||||
.select('project')
|
||||
.from('group_role')
|
||||
.leftJoin(
|
||||
'group_user',
|
||||
'group_user.group_id',
|
||||
'group_role.group_id',
|
||||
)
|
||||
.where('user_id', userId);
|
||||
})
|
||||
.as('query');
|
||||
});
|
||||
return members;
|
||||
}
|
||||
|
||||
async getMembersCountByProject(projectId: string): Promise<number> {
|
||||
const members = await this.db
|
||||
.from((db) => {
|
||||
db.select('user_id')
|
||||
|
@ -120,6 +120,7 @@ import { publicSignupTokenSchema } from './spec/public-signup-token-schema';
|
||||
import { publicSignupTokensSchema } from './spec/public-signup-tokens-schema';
|
||||
import { publicSignupTokenUpdateSchema } from './spec/public-signup-token-update-schema';
|
||||
import apiVersion from '../util/version';
|
||||
import { profileSchema } from './spec/profile-schema';
|
||||
|
||||
// All schemas in `openapi/spec` should be listed here.
|
||||
export const schemas = {
|
||||
@ -196,6 +197,7 @@ export const schemas = {
|
||||
publicSignupTokenUpdateSchema,
|
||||
publicSignupTokensSchema,
|
||||
publicSignupTokenSchema,
|
||||
profileSchema,
|
||||
proxyClientSchema,
|
||||
proxyFeaturesSchema,
|
||||
proxyFeatureSchema,
|
||||
|
18
src/lib/openapi/spec/profile-schema.test.ts
Normal file
18
src/lib/openapi/spec/profile-schema.test.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { validateSchema } from '../validate';
|
||||
import { ProfileSchema } from './profile-schema';
|
||||
import { RoleName } from '../../types/model';
|
||||
|
||||
test('profileSchema', () => {
|
||||
const data: ProfileSchema = {
|
||||
rootRole: 'Editor' as RoleName,
|
||||
projects: ['default', 'secretproject'],
|
||||
features: [
|
||||
{ name: 'firstFeature', project: 'default' },
|
||||
{ name: 'secondFeature', project: 'secretproject' },
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
validateSchema('#/components/schemas/profileSchema', data),
|
||||
).toBeUndefined();
|
||||
});
|
35
src/lib/openapi/spec/profile-schema.ts
Normal file
35
src/lib/openapi/spec/profile-schema.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
import { featureSchema } from './feature-schema';
|
||||
import { RoleName } from '../../types/model';
|
||||
|
||||
export const profileSchema = {
|
||||
$id: '#/components/schemas/profileSchema',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['rootRole', 'projects', 'features'],
|
||||
properties: {
|
||||
rootRole: {
|
||||
type: 'string',
|
||||
enum: Object.values(RoleName),
|
||||
},
|
||||
projects: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
features: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/featureSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
featureSchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type ProfileSchema = FromSchema<typeof profileSchema>;
|
@ -16,6 +16,12 @@ import { serializeDates } from '../../../types/serialize-dates';
|
||||
import { IUserPermission } from '../../../types/stores/access-store';
|
||||
import { PasswordSchema } from '../../../openapi/spec/password-schema';
|
||||
import { emptyResponse } from '../../../openapi/util/standard-responses';
|
||||
import {
|
||||
profileSchema,
|
||||
ProfileSchema,
|
||||
} from '../../../openapi/spec/profile-schema';
|
||||
import ProjectService from '../../../services/project-service';
|
||||
import { RoleName } from '../../../types/model';
|
||||
|
||||
class UserController extends Controller {
|
||||
private accessService: AccessService;
|
||||
@ -28,6 +34,8 @@ class UserController extends Controller {
|
||||
|
||||
private openApiService: OpenApiService;
|
||||
|
||||
private projectService: ProjectService;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
{
|
||||
@ -36,6 +44,7 @@ class UserController extends Controller {
|
||||
userFeedbackService,
|
||||
userSplashService,
|
||||
openApiService,
|
||||
projectService,
|
||||
}: Pick<
|
||||
IUnleashServices,
|
||||
| 'accessService'
|
||||
@ -43,6 +52,7 @@ class UserController extends Controller {
|
||||
| 'userFeedbackService'
|
||||
| 'userSplashService'
|
||||
| 'openApiService'
|
||||
| 'projectService'
|
||||
>,
|
||||
) {
|
||||
super(config);
|
||||
@ -51,6 +61,7 @@ class UserController extends Controller {
|
||||
this.userFeedbackService = userFeedbackService;
|
||||
this.userSplashService = userSplashService;
|
||||
this.openApiService = openApiService;
|
||||
this.projectService = projectService;
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
@ -66,6 +77,20 @@ class UserController extends Controller {
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
path: '/profile',
|
||||
handler: this.getProfile,
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Users'],
|
||||
operationId: 'getProfile',
|
||||
responses: { 200: createResponseSchema('profileSchema') },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'post',
|
||||
path: '/change-password',
|
||||
@ -114,6 +139,30 @@ class UserController extends Controller {
|
||||
);
|
||||
}
|
||||
|
||||
async getProfile(
|
||||
req: IAuthRequest,
|
||||
res: Response<ProfileSchema>,
|
||||
): Promise<void> {
|
||||
const { user } = req;
|
||||
|
||||
const projects = await this.projectService.getProjectsByUser(user.id);
|
||||
|
||||
const roles = await this.accessService.getUserRootRoles(user.id);
|
||||
|
||||
const responseData: ProfileSchema = {
|
||||
projects,
|
||||
rootRole: roles[0].name as RoleName,
|
||||
features: [],
|
||||
};
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
profileSchema.$id,
|
||||
responseData,
|
||||
);
|
||||
}
|
||||
|
||||
async changeMyPassword(
|
||||
req: IAuthRequest<unknown, unknown, PasswordSchema>,
|
||||
res: Response,
|
||||
|
@ -574,6 +574,10 @@ export default class ProjectService {
|
||||
return this.store.getMembersCountByProject(projectId);
|
||||
}
|
||||
|
||||
async getProjectsByUser(userId: number): Promise<string[]> {
|
||||
return this.store.getProjectsByUser(userId);
|
||||
}
|
||||
|
||||
async getProjectOverview(
|
||||
projectId: string,
|
||||
archived: boolean = false,
|
||||
|
@ -36,6 +36,7 @@ export interface IProjectStore extends Store<IProject, string> {
|
||||
deleteEnvironmentForProject(id: string, environment: string): Promise<void>;
|
||||
getEnvironmentsForProject(id: string): Promise<string[]>;
|
||||
getMembersCountByProject(projectId: string): Promise<number>;
|
||||
getProjectsByUser(userId: number): Promise<string[]>;
|
||||
getMembersCount(): Promise<IProjectMembersCount[]>;
|
||||
getProjectsWithCounts(query?: IProjectQuery): Promise<IProjectWithCount[]>;
|
||||
count(): Promise<number>;
|
||||
|
@ -2128,6 +2128,39 @@ exports[`should serve the OpenAPI spec 1`] = `
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"profileSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"features": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/featureSchema",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"projects": {
|
||||
"items": {
|
||||
"type": "string",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"rootRole": {
|
||||
"enum": [
|
||||
"Admin",
|
||||
"Editor",
|
||||
"Viewer",
|
||||
"Owner",
|
||||
"Member",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"rootRole",
|
||||
"projects",
|
||||
"features",
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"projectEnvironmentSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@ -7013,6 +7046,26 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/user/profile": {
|
||||
"get": {
|
||||
"operationId": "getProfile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/profileSchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "profileSchema",
|
||||
},
|
||||
},
|
||||
"tags": [
|
||||
"Users",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/user/tokens": {
|
||||
"get": {
|
||||
"operationId": "getPats",
|
||||
|
5
src/test/fixtures/fake-project-store.ts
vendored
5
src/test/fixtures/fake-project-store.ts
vendored
@ -127,4 +127,9 @@ export default class FakeProjectStore implements IProjectStore {
|
||||
getMembersCount(): Promise<IProjectMembersCount[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getProjectsByUser(userId: number): Promise<string[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user