From 68e7e76b315d681aed8bb0b3fb3ff9ad680ce0a2 Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:41:47 +0300 Subject: [PATCH] feat: list managed users of an oauth client (#14330) * feat: list managed users with apiv2 api * refactor: isPending -> isLoading * Revert "refactor: isPending -> isLoading" This reverts commit dc2108e05508070185d57633346f3c532458e2a8. --------- Co-authored-by: supalarry --- .../oauth-client-users.controller.e2e-spec.ts | 15 +++++++++++ .../oauth-client-users.controller.ts | 24 +++++++++++++++++- .../v2/src/modules/users/users.repository.ts | 15 +++++++++++ apps/api/v2/swagger/documentation.json | 25 +++++++++++++++++++ apps/api/v2/test/setEnvVars.ts | 3 ++- packages/platform/types/api.ts | 18 +++++++++++++ 6 files changed, 98 insertions(+), 2 deletions(-) diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts index fd9fce0f15..c09aa43808 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts @@ -179,6 +179,21 @@ describe("OAuth Client Users Endpoints", () => { ).toBeTruthy(); } + it(`/GET: return list of managed users`, async () => { + const response = await request(app.getHttpServer()) + .get(`/api/v2/oauth-clients/${oAuthClient.id}/users?limit=10&offset=0`) + .set("x-cal-secret-key", oAuthClient.secret) + .set("Origin", `${CLIENT_REDIRECT_URI}`) + .expect(200); + + const responseBody: ApiSuccessResponse = response.body; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data?.length).toBeGreaterThan(0); + expect(responseBody.data[0].email).toEqual(postResponseData.user.email); + }); + it(`/GET/:id`, async () => { const response = await request(app.getHttpServer()) .get(`/api/v2/oauth-clients/${oAuthClient.id}/users/${postResponseData.user.id}`) diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts index 26a92ab3cd..1cc9914135 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts @@ -20,11 +20,12 @@ import { Patch, BadRequestException, Delete, + Query, } from "@nestjs/common"; import { User } from "@prisma/client"; import { SUCCESS_STATUS } from "@calcom/platform-constants"; -import { ApiResponse } from "@calcom/platform-types"; +import { ApiResponse, Pagination } from "@calcom/platform-types"; @Controller({ path: "oauth-clients/:clientId/users", @@ -39,6 +40,27 @@ export class OAuthClientUsersController { private readonly oauthRepository: OAuthClientRepository ) {} + @Get("/") + @UseGuards(OAuthClientCredentialsGuard) + async getManagedUsers( + @Param("clientId") oAuthClientId: string, + @Query() queryParams: Pagination + ): Promise> { + this.logger.log(`getting managed users with data for OAuth Client with ID ${oAuthClientId}`); + const { offset, limit } = queryParams; + + const existingUsers = await this.userRepository.findManagedUsersByOAuthClientId( + oAuthClientId, + offset ?? 0, + limit ?? 50 + ); + + return { + status: SUCCESS_STATUS, + data: existingUsers, + }; + } + @Post("/") @UseGuards(OAuthClientCredentialsGuard) async createUser( diff --git a/apps/api/v2/src/modules/users/users.repository.ts b/apps/api/v2/src/modules/users/users.repository.ts index 57555ce1c3..ed68ecea26 100644 --- a/apps/api/v2/src/modules/users/users.repository.ts +++ b/apps/api/v2/src/modules/users/users.repository.ts @@ -102,6 +102,21 @@ export class UsersRepository { }); } + async findManagedUsersByOAuthClientId(oauthClientId: string, cursor: number, limit: number) { + return this.dbRead.prisma.user.findMany({ + where: { + platformOAuthClients: { + some: { + id: oauthClientId, + }, + }, + isPlatformManaged: true, + }, + take: limit, + skip: cursor, + }); + } + async update(userId: number, updateData: UpdateManagedPlatformUserInput) { this.formatInput(updateData); diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index ec595b86be..d573e7ab53 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -71,6 +71,31 @@ } }, "/api/v2/oauth-clients/{clientId}/users": { + "get": { + "operationId": "OAuthClientUsersController_getManagedUsers", + "parameters": [ + { + "name": "clientId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, "post": { "operationId": "OAuthClientUsersController_createUser", "parameters": [ diff --git a/apps/api/v2/test/setEnvVars.ts b/apps/api/v2/test/setEnvVars.ts index d353423dfa..a0e9bce905 100644 --- a/apps/api/v2/test/setEnvVars.ts +++ b/apps/api/v2/test/setEnvVars.ts @@ -9,7 +9,8 @@ const env: Partial> = { LOG_LEVEL: "trace", REDIS_URL: "redis://localhost:9199", }; - +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore process.env = { ...env, ...process.env, diff --git a/packages/platform/types/api.ts b/packages/platform/types/api.ts index 6b067f2869..b2039ed22f 100644 --- a/packages/platform/types/api.ts +++ b/packages/platform/types/api.ts @@ -1,3 +1,5 @@ +import { Transform } from "class-transformer"; +import { IsNumber, Min, Max, IsOptional } from "class-validator"; import type { Response as BaseResponse } from "express"; import type { ERROR_STATUS, SUCCESS_STATUS, API_ERROR_CODES } from "@calcom/platform-constants"; @@ -29,3 +31,19 @@ export type Response = BaseResponse>; export type ApiResponse = T extends undefined ? ApiSuccessResponseWithoutData | ApiErrorResponse : ApiSuccessResponse | ApiErrorResponse; + +export class Pagination { + @Transform(({ value }: { value: string }) => value && parseInt(value)) + @IsNumber() + @Min(1) + @Max(100) + @IsOptional() + limit?: number; + + @Transform(({ value }: { value: string }) => value && parseInt(value)) + @IsNumber() + @Min(0) + @Max(100) + @IsOptional() + offset?: number | null; +}