Refactor GET /users and /users/:identifier

This commit is contained in:
ricardo 2022-04-06 20:35:48 -04:00
parent a20bc33c7d
commit 02a88272de
5 changed files with 103 additions and 76 deletions

View file

@ -222,16 +222,16 @@ export async function inviteUsers(
});
}
export async function getUserByIdentifier(
identifier: string,
options?: { includeRole: boolean },
): Promise<User | undefined> {
return Db.collections.User?.findOneOrFail({
export async function getUser(data: {
withIdentifier: string;
includeRole: boolean;
}): Promise<User | undefined> {
return Db.collections.User?.findOne({
where: {
...(uuidValidate(identifier) && { id: identifier }),
...(!uuidValidate(identifier) && { email: identifier }),
...(uuidValidate(data.withIdentifier) && { id: data.withIdentifier }),
...(!uuidValidate(data.withIdentifier) && { email: data.withIdentifier }),
},
relations: options?.includeRole ? ['globalRole'] : undefined,
relations: data?.includeRole ? ['globalRole'] : undefined,
});
}
@ -248,6 +248,26 @@ export async function getUsers(data: {
});
}
export async function getAllUsersAndCount(data: {
includeRole?: boolean;
limit?: number;
offset?: number;
}): Promise<[User[], number]> {
console.log({
relations: data?.includeRole ? ['globalRole'] : undefined,
skip: data.offset,
take: data.limit,
})
const users = await Db.collections.User!.find({
where: {},
relations: data?.includeRole ? ['globalRole'] : undefined,
skip: data.offset,
take: data.limit,
});
const count = await Db.collections.User!.count();
return [users, count];
}
export async function transferWorkflowsAndCredentials(data: {
fromUser: User;
toUser: User;

View file

@ -1,9 +1,12 @@
/* eslint-disable import/no-cycle */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable consistent-return */
/* eslint-disable @typescript-eslint/no-explicit-any */
import express = require('express');
import validator from 'validator';
import config = require('../../config');
import type { UserRequest } from '../requests';
import { decodeCursor } from './helpers';
type Role = 'owner' | 'member';
@ -84,6 +87,43 @@ const transferingToDeletedUser = (
next();
};
const validCursor = (
req: UserRequest.Get,
res: express.Response,
next: express.NextFunction,
): any => {
let offset = 0;
let limit = 10;
if (req.query?.limit) {
limit = parseInt(req.query?.limit, 10) || 10;
}
if (req.query.cursor) {
const { cursor } = req.query;
try {
({ offset, limit } = decodeCursor(cursor));
} catch (error) {
return res.status(400).json({
message: `invalid cursor`,
});
}
}
req.limit = limit;
req.offset = offset;
next();
};
const parseIncludeRole = (
req: UserRequest.Get,
res: express.Response,
next: express.NextFunction,
): any => {
req.includeRole = false;
if (req.query?.includeRole) {
req.includeRole = req.query.includeRole === 'true';
}
next();
};
export const middlewares = {
createUsers: [instanceOwnerSetup, emailSetup, validEmail, authorize(['owner'])],
deleteUsers: [
@ -92,6 +132,6 @@ export const middlewares = {
transferingToDeletedUser,
authorize(['owner']),
],
getUsers: [instanceOwnerSetup, authorize(['owner'])],
getUser: [instanceOwnerSetup, authorize(['owner'])],
getUsers: [instanceOwnerSetup, parseIncludeRole, validCursor, authorize(['owner'])],
getUser: [instanceOwnerSetup, parseIncludeRole, authorize(['owner'])],
};

View file

@ -10,12 +10,12 @@ import { Role } from '../../../../databases/entities/Role';
import {
clean,
connectionName,
decodeCursor,
deleteDataAndSendTelemetry,
getAllUsersAndCount,
getGlobalMemberRole,
getNextCursor,
getSelectableProperties,
getUser,
getUsers,
getUsersToSaveAndInvite,
inviteUsers,
@ -112,74 +112,35 @@ export = {
return clean(userToDelete);
},
// eslint-disable-next-line consistent-return
getUser: async (req: UserRequest.Get, res: express.Response): Promise<any> => {
const includeRole = req.query?.includeRole?.toLowerCase() === 'true' || false;
getUser: ResponseHelper.send(async (req: UserRequest.Get, res: express.Response) => {
const { includeRole } = req;
const { identifier } = req.params;
const query = getConnection(connectionName())
.getRepository(User)
.createQueryBuilder()
.leftJoinAndSelect('User.globalRole', 'Role')
.select(getSelectableProperties('user')?.map((property) => `User.${property}`));
const user = await getUser({ withIdentifier: identifier, includeRole });
if (includeRole) {
query.addSelect(getSelectableProperties('role')?.map((property) => `Role.${property}`));
if (!user) {
throw new ResponseHelper.ResponseError(
`Could not find user with identifier: ${identifier}`,
undefined,
404,
);
}
if (uuidValidate(identifier)) {
query.where({ id: identifier });
} else {
query.where({ email: identifier });
}
const user = await query.getOne();
if (user === undefined) {
return res.status(404);
}
res.json(user);
},
return clean(user);
}),
// eslint-disable-next-line consistent-return
getUsers: async (
req: UserRequest.Get,
res: express.Response,
// eslint-disable-next-line @typescript-eslint/no-shadow
next: express.NextFunction,
// eslint-disable-next-line consistent-return
): Promise<any> => {
let offset = 0;
let limit = parseInt(req.query.limit, 10) || 10;
const includeRole = req.query?.includeRole?.toLowerCase() === 'true' || false;
getUsers: ResponseHelper.send(async (req: UserRequest.Get, res: express.Response) => {
const { offset, limit, includeRole } = req;
if (req.query.cursor) {
const { cursor } = req.query;
try {
({ offset, limit } = decodeCursor(cursor));
} catch (error) {
return res.status(400).json({
message: 'Invalid cursor',
});
}
}
const query = getConnection(connectionName())
.getRepository(User)
.createQueryBuilder()
.leftJoinAndSelect('User.globalRole', 'Role')
.select(getSelectableProperties('user')?.map((property) => `User.${property}`))
.limit(limit)
.offset(offset);
if (includeRole) {
query.addSelect(getSelectableProperties('role')?.map((property) => `Role.${property}`));
}
const [users, count] = await query.getManyAndCount();
res.json({
users,
nextCursor: getNextCursor(offset, limit, count),
const [users, count] = await getAllUsersAndCount({
includeRole,
limit,
offset,
});
},
return {
users: clean(users, { includeRole }),
nextCursor: getNextCursor(offset, limit, count),
};
}),
};

View file

@ -149,6 +149,7 @@ const isUniqueConstraintError = (error: Error) =>
*/
export function send(processFunction: (req: Request, res: Response) => Promise<any>) {
// eslint-disable-next-line consistent-return
return async (req: Request, res: Response) => {
try {
const data = await processFunction(req, res);

View file

@ -26,7 +26,12 @@ export type AuthenticatedRequest<
ResponseBody = {},
RequestBody = {},
RequestQuery = {},
> = express.Request<RouteParams, ResponseBody, RequestBody, RequestQuery> & { user: User };
> = express.Request<RouteParams, ResponseBody, RequestBody, RequestQuery> & {
user: User;
limit: number;
offset: number;
includeRole: boolean;
};
// ----------------------------------
// /workflows
@ -207,7 +212,7 @@ export declare namespace UserRequest {
{ id: string; email: string; identifier: string },
{},
{},
{ limit: string; cursor: string; includeRole: string }
{ limit?: string; offset: string; cursor?: string; includeRole?: string }
>;
export type Reinvite = AuthenticatedRequest<{ id: string }>;