diff --git a/packages/cli/src/PublicApi/helpers.ts b/packages/cli/src/PublicApi/helpers.ts index 27a001367b..7b20ea6dbf 100644 --- a/packages/cli/src/PublicApi/helpers.ts +++ b/packages/cli/src/PublicApi/helpers.ts @@ -222,16 +222,16 @@ export async function inviteUsers( }); } -export async function getUserByIdentifier( - identifier: string, - options?: { includeRole: boolean }, -): Promise { - return Db.collections.User?.findOneOrFail({ +export async function getUser(data: { + withIdentifier: string; + includeRole: boolean; +}): Promise { + 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; diff --git a/packages/cli/src/PublicApi/middlewares.ts b/packages/cli/src/PublicApi/middlewares.ts index 1c89c91e34..a0b557e9b1 100644 --- a/packages/cli/src/PublicApi/middlewares.ts +++ b/packages/cli/src/PublicApi/middlewares.ts @@ -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'])], }; diff --git a/packages/cli/src/PublicApi/v1/routes/Users/index.ts b/packages/cli/src/PublicApi/v1/routes/Users/index.ts index 543a1d41ae..96e84d20f7 100644 --- a/packages/cli/src/PublicApi/v1/routes/Users/index.ts +++ b/packages/cli/src/PublicApi/v1/routes/Users/index.ts @@ -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 => { - 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 => { - 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), + }; + }), }; diff --git a/packages/cli/src/ResponseHelper.ts b/packages/cli/src/ResponseHelper.ts index 4a077fa726..d298b068a0 100644 --- a/packages/cli/src/ResponseHelper.ts +++ b/packages/cli/src/ResponseHelper.ts @@ -149,6 +149,7 @@ const isUniqueConstraintError = (error: Error) => */ export function send(processFunction: (req: Request, res: Response) => Promise) { + // eslint-disable-next-line consistent-return return async (req: Request, res: Response) => { try { const data = await processFunction(req, res); diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index b7699d02b1..7882fb2aa2 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -26,7 +26,12 @@ export type AuthenticatedRequest< ResponseBody = {}, RequestBody = {}, RequestQuery = {}, -> = express.Request & { user: User }; +> = express.Request & { + 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 }>;