From 161e598809935cf13ca5497743b201cd36bc5476 Mon Sep 17 00:00:00 2001 From: Mir Arif Hasan Date: Tue, 29 Oct 2024 00:23:53 +0600 Subject: [PATCH] feat: user workspace stats api added (#4467) (HSB-495) Co-authored-by: Andrew Bastin --- .../src/infra-token/infra-token.controller.ts | 30 +++++++++-- .../src/infra-token/request-response.dto.ts | 32 ++++++++++++ .../src/user/user.service.ts | 50 +++++++++++++++++++ 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/packages/hoppscotch-backend/src/infra-token/infra-token.controller.ts b/packages/hoppscotch-backend/src/infra-token/infra-token.controller.ts index 77ddbc025..21e55ed13 100644 --- a/packages/hoppscotch-backend/src/infra-token/infra-token.controller.ts +++ b/packages/hoppscotch-backend/src/infra-token/infra-token.controller.ts @@ -28,6 +28,7 @@ import { CreateUserInvitationRequest, CreateUserInvitationResponse, DeleteUserResponse, + GetUserWorkspacesResponse, } from './request-response.dto'; import * as E from 'fp-ts/Either'; import * as O from 'fp-ts/Option'; @@ -104,9 +105,8 @@ export class InfraTokensController { async getPendingUserInvitation( @Query() paginationQuery: OffsetPaginationArgs, ) { - const pendingInvitedUsers = await this.adminService.fetchInvitedUsers( - paginationQuery, - ); + const pendingInvitedUsers = + await this.adminService.fetchInvitedUsers(paginationQuery); return plainToInstance(GetUserInvitationResponse, pendingInvitedUsers, { excludeExtraneousValues: true, @@ -275,4 +275,28 @@ export class InfraTokensController { }, ); } + + @Get('users/:uid/workspaces') + @ApiOkResponse({ + description: 'Get user workspaces', + type: [GetUserWorkspacesResponse], + }) + @ApiNotFoundResponse({ type: ExceptionResponse }) + async getUserWorkspaces(@Param('uid') uid: string) { + const userWorkspaces = await this.userService.fetchUserWorkspaces(uid); + + if (E.isLeft(userWorkspaces)) { + const statusCode = + userWorkspaces.left === USER_NOT_FOUND + ? HttpStatus.NOT_FOUND + : HttpStatus.BAD_REQUEST; + + throwHTTPErr({ message: userWorkspaces.left, statusCode }); + } + + return plainToInstance(GetUserWorkspacesResponse, userWorkspaces.right, { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }); + } } diff --git a/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts b/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts index 8e71b0e61..f3c64f8fc 100644 --- a/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts +++ b/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts @@ -10,6 +10,7 @@ import { IsString, MinLength, } from 'class-validator'; +import { TeamMemberRole } from 'src/team/team.model'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; // POST v1/infra/user-invitations @@ -128,3 +129,34 @@ export class DeleteUserResponse { @Expose() message: string; } + +// GET v1/infra/users/:uid/workspaces +export class GetUserWorkspacesResponse { + @ApiProperty() + @Expose() + id: string; + + @ApiProperty() + @Expose() + name: string; + + @ApiProperty({ enum: TeamMemberRole }) + @Expose() + role: string; + + @ApiProperty() + @Expose() + owner_count: number; + + @ApiProperty() + @Expose() + editor_count: number; + + @ApiProperty() + @Expose() + viewer_count: number; + + @ApiProperty() + @Expose() + member_count: number; +} diff --git a/packages/hoppscotch-backend/src/user/user.service.ts b/packages/hoppscotch-backend/src/user/user.service.ts index 9ccbdc27d..c3162e85a 100644 --- a/packages/hoppscotch-backend/src/user/user.service.ts +++ b/packages/hoppscotch-backend/src/user/user.service.ts @@ -20,6 +20,8 @@ import { encrypt, stringToJson, taskEitherValidateArraySeq } from 'src/utils'; import { UserDataHandler } from './user.data.handler'; import { User as DbUser } from '@prisma/client'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; +import { GetUserWorkspacesResponse } from 'src/infra-token/request-response.dto'; +import { TeamMemberRole } from 'src/team/team.model'; @Injectable() export class UserService { @@ -598,4 +600,52 @@ export class UserService { return E.right(true); } + + async fetchUserWorkspaces(userUid: string) { + const user = await this.prisma.user.findUnique({ where: { uid: userUid } }); + if (!user) return E.left(USER_NOT_FOUND); + + const team = await this.prisma.team.findMany({ + where: { + members: { + some: { + userUid, + }, + }, + }, + include: { + members: { + select: { + userUid: true, + role: true, + }, + }, + }, + }); + + const workspaces: GetUserWorkspacesResponse[] = []; + team.forEach((t) => { + const ownerCount = t.members.filter( + (m) => m.role === TeamMemberRole.OWNER, + ).length; + const editorCount = t.members.filter( + (m) => m.role === TeamMemberRole.EDITOR, + ).length; + const viewerCount = t.members.filter( + (m) => m.role === TeamMemberRole.VIEWER, + ).length; + const memberCount = t.members.length; + + workspaces.push({ + id: t.id, + name: t.name, + role: t.members.find((m) => m.userUid === userUid)?.role, + owner_count: ownerCount, + editor_count: editorCount, + viewer_count: viewerCount, + member_count: memberCount, + }); + }); + return E.right(workspaces); + } }