From 2aafa1c1631d87824d103f1fa0580978a1eb0c60 Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Tue, 27 May 2025 12:50:31 +0200 Subject: [PATCH] feat: v2 managed organizations pagination (#21359) * feat: getPagination helper * refactor: bookings use getPagination helper * feat: getPagination helper * refactor: group pagination inputs and outputs in types/pagination * feat: paginated managed orgs * fix tests * swagger --- .../2024-08-13/services/bookings.service.ts | 31 +- .../v2/src/lib/pagination/pagination.spec.ts | 101 +++++ apps/api/v2/src/lib/pagination/pagination.ts | 78 ++++ .../managed-organizations.repository.ts | 28 +- ...tions-organizations.controller.e2e-spec.ts | 11 +- .../organizations-organizations.controller.ts | 11 +- .../get-managed-organizations.output.ts | 6 + .../services/managed-organizations.service.ts | 28 +- apps/api/v2/swagger/documentation.json | 365 ++++++++++++------ docs/api-reference/v2/openapi.json | 364 +++++++++++------ packages/platform/types/api.ts | 39 +- .../2024-08-13/outputs/get-bookings.output.ts | 65 +--- packages/platform/types/index.ts | 2 + .../types/pagination/pagination.input.ts | 52 +++ .../types/pagination/pagination.output.ts | 72 ++++ 15 files changed, 878 insertions(+), 375 deletions(-) create mode 100644 apps/api/v2/src/lib/pagination/pagination.spec.ts create mode 100644 apps/api/v2/src/lib/pagination/pagination.ts create mode 100644 packages/platform/types/pagination/pagination.input.ts create mode 100644 packages/platform/types/pagination/pagination.output.ts diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts index c1b16737a5..5bebe6abd1 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts @@ -5,6 +5,7 @@ import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/servic import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service"; import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; +import { getPagination } from "@/lib/pagination/pagination"; import { BillingService } from "@/modules/billing/services/billing.service"; import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository"; import { KyselyReadService } from "@/modules/kysely/kysely-read.service"; @@ -538,10 +539,13 @@ export class BookingsService_2024_08_13 { queryParams.attendeeEmail = await this.getAttendeeEmail(queryParams.attendeeEmail, user); } + const skip = Math.abs(queryParams?.skip ?? 0); + const take = Math.abs(queryParams?.take ?? 100); + const fetchedBookings: { bookings: { id: number }[]; totalCount: number } = await getAllUserBookings({ bookingListingByStatus: queryParams.status || [], - skip: queryParams.skip ?? 0, - take: queryParams.take ?? 100, + skip, + take, filters: { ...this.inputService.transformGetBookingsFilters(queryParams), ...(userIds?.length ? { userIds } : {}), @@ -594,28 +598,11 @@ export class BookingsService_2024_08_13 { } } - const skip = Math.abs(queryParams?.skip ?? 0); - const take = Math.abs(queryParams?.take ?? 100); - const itemsPerPage = take; - const totalPages = itemsPerPage !== 0 ? Math.ceil(fetchedBookings.totalCount / itemsPerPage) : 0; - const currentPage = Math.floor(skip / itemsPerPage) + 1; - const hasNextPage = skip + itemsPerPage < fetchedBookings.totalCount; - const hasPreviousPage = skip > 0; + const pagination = getPagination({ skip, take, totalCount: fetchedBookings.totalCount }); + return { bookings: formattedBookings, - pagination: { - totalItems: fetchedBookings.totalCount, - // clamp remainingItems between 0 and totalCount - remainingItems: Math.min( - Math.max(fetchedBookings.totalCount - (skip + take), 0), - fetchedBookings.totalCount - ), - itemsPerPage: itemsPerPage, - currentPage: currentPage, - totalPages: totalPages, - hasNextPage: hasNextPage, - hasPreviousPage: hasPreviousPage, - }, + pagination, }; } diff --git a/apps/api/v2/src/lib/pagination/pagination.spec.ts b/apps/api/v2/src/lib/pagination/pagination.spec.ts new file mode 100644 index 0000000000..6d44860bdc --- /dev/null +++ b/apps/api/v2/src/lib/pagination/pagination.spec.ts @@ -0,0 +1,101 @@ +import { getPagination, clamp } from "./pagination"; + +describe("getPagination", () => { + it("handles the first page correctly", () => { + const pagination = getPagination({ skip: 0, take: 10, totalCount: 35 }); + expect(pagination).toEqual({ + returnedItems: 10, + totalItems: 35, + itemsPerPage: 10, + remainingItems: 25, + currentPage: 1, + totalPages: 4, + hasNextPage: true, + hasPreviousPage: false, + }); + }); + + it("handles a middle page correctly", () => { + const pagination = getPagination({ skip: 10, take: 10, totalCount: 35 }); + expect(pagination).toEqual({ + returnedItems: 10, + totalItems: 35, + itemsPerPage: 10, + remainingItems: 15, + currentPage: 2, + totalPages: 4, + hasNextPage: true, + hasPreviousPage: true, + }); + }); + + it("handles the last page when it is not completely full", () => { + const pagination = getPagination({ skip: 30, take: 10, totalCount: 35 }); + expect(pagination).toEqual({ + returnedItems: 5, + totalItems: 35, + itemsPerPage: 10, + remainingItems: 0, + currentPage: 4, + totalPages: 4, + hasNextPage: false, + hasPreviousPage: true, + }); + }); + + it("clamps skip values that exceed the total item count", () => { + const pagination = getPagination({ skip: 40, take: 10, totalCount: 35 }); + expect(pagination).toEqual({ + returnedItems: 0, + totalItems: 35, + itemsPerPage: 10, + remainingItems: 0, + currentPage: 4, + totalPages: 4, + hasNextPage: false, + hasPreviousPage: true, + }); + }); + + it("works when itemsPerPage (take) is zero", () => { + const pagination = getPagination({ skip: 0, take: 0, totalCount: 35 }); + expect(pagination).toEqual({ + returnedItems: 0, + totalItems: 35, + itemsPerPage: 0, + remainingItems: 35, + currentPage: 0, + totalPages: 0, + hasNextPage: true, + hasPreviousPage: false, + }); + }); + + it("works when the collection is empty", () => { + const pagination = getPagination({ skip: 0, take: 10, totalCount: 0 }); + expect(pagination).toEqual({ + returnedItems: 0, + totalItems: 0, + itemsPerPage: 10, + remainingItems: 0, + currentPage: 0, + totalPages: 0, + hasNextPage: false, + hasPreviousPage: false, + }); + }); +}); + +describe("clamp", () => { + it("returns the value unchanged when it is inside the range", () => { + expect(clamp({ value: 5, min: 1, max: 10 })).toBe(5); + }); + + it("clamps values below the minimum", () => { + expect(clamp({ value: -3, min: 0, max: 10 })).toBe(0); + }); + + it("clamps values above the maximum", () => { + expect(clamp({ value: 42, min: 0, max: 10 })).toBe(10); + }); +}); diff --git a/apps/api/v2/src/lib/pagination/pagination.ts b/apps/api/v2/src/lib/pagination/pagination.ts new file mode 100644 index 0000000000..61a564f8f7 --- /dev/null +++ b/apps/api/v2/src/lib/pagination/pagination.ts @@ -0,0 +1,78 @@ +import { PaginationMetaDto } from "@calcom/platform-types"; + +type Pagination = { + skip: number; + take: number; + totalCount: number; +}; + +export function getPagination(pagination: Pagination): PaginationMetaDto { + const { skip, take, totalCount } = pagination; + + const safeSkip = clamp({ value: skip, min: 0, max: totalCount }); + const itemsPerPage = take; + const remainingItems = getRemainingItems(safeSkip, itemsPerPage, totalCount); + const totalPages = getTotalPages(itemsPerPage, totalCount); + const currentPage = getCurrentPage(safeSkip, itemsPerPage, totalPages); + const hasNextPage = getHasNextPage(safeSkip, itemsPerPage, totalCount); + const hasPreviousPage = getHasPreviousPage(safeSkip); + const returnedItems = getReturnedItems(safeSkip, itemsPerPage, totalCount); + + return { + returnedItems, + totalItems: totalCount, + itemsPerPage, + remainingItems, + currentPage, + totalPages, + hasNextPage, + hasPreviousPage, + }; +} + +function getRemainingItems(skip: number, itemsPerPage: number, totalCount: number) { + return clamp({ + value: totalCount - (skip + itemsPerPage), + min: 0, + max: totalCount, + }); +} + +function getTotalPages(itemsPerPage: number, totalCount: number) { + return itemsPerPage !== 0 ? Math.ceil(totalCount / itemsPerPage) : 0; +} + +function getCurrentPage(skip: number, itemsPerPage: number, totalPages: number) { + const rawCurrentPage = Math.floor(skip / itemsPerPage) + 1; + if (totalPages === 0) { + return 0; + } + return clamp({ value: rawCurrentPage, min: 1, max: totalPages }); +} + +function getHasNextPage(skip: number, itemsPerPage: number, totalCount: number) { + return skip + itemsPerPage < totalCount; +} + +function getHasPreviousPage(skip: number) { + return skip > 0; +} + +function getReturnedItems(skip: number, itemsPerPage: number, totalCount: number) { + const remaining = totalCount - skip; + return clamp({ + value: remaining, + min: 0, + max: itemsPerPage, + }); +} + +type ClampArgs = { + value: number; + min: number; + max: number; +}; + +export function clamp({ value, min, max }: ClampArgs): number { + return Math.max(min, Math.min(value, max)); +} diff --git a/apps/api/v2/src/modules/organizations/organizations/managed-organizations.repository.ts b/apps/api/v2/src/modules/organizations/organizations/managed-organizations.repository.ts index e859dd3cf0..4d4aeef114 100644 --- a/apps/api/v2/src/modules/organizations/organizations/managed-organizations.repository.ts +++ b/apps/api/v2/src/modules/organizations/organizations/managed-organizations.repository.ts @@ -2,6 +2,7 @@ import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; import { Injectable } from "@nestjs/common"; +import { SkipTakePagination } from "@calcom/platform-types"; import { Prisma } from "@calcom/prisma/client"; @Injectable() @@ -34,11 +35,26 @@ export class ManagedOrganizationsRepository { }); } - async getByManagerOrganizationId(managerOrganizationId: number) { - return this.dbRead.prisma.managedOrganization.findMany({ - where: { - managerOrganizationId, - }, - }); + async getByManagerOrganizationIdPaginated(managerOrganizationId: number, pagination: SkipTakePagination) { + const { skip, take } = pagination; + + const where: Prisma.ManagedOrganizationWhereInput = { + managerOrganizationId, + }; + + const [totalItems, linkRows] = await this.dbRead.prisma.$transaction([ + this.dbRead.prisma.managedOrganization.count({ where }), + this.dbRead.prisma.managedOrganization.findMany({ + where, + skip, + take, + orderBy: { managedOrganizationId: "asc" }, + include: { managedOrganization: true }, + }), + ]); + + const items = linkRows.map((l) => l.managedOrganization); + + return { totalItems, items }; } } diff --git a/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.e2e-spec.ts b/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.e2e-spec.ts index 0e3a43f1a2..a2186d3d62 100644 --- a/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.e2e-spec.ts @@ -7,6 +7,7 @@ import { CreateOAuthClientResponseDto } from "@/modules/oauth-clients/controller import { GetOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientResponse.dto"; import { CreateOrganizationInput } from "@/modules/organizations/organizations/inputs/create-managed-organization.input"; import { UpdateOrganizationInput } from "@/modules/organizations/organizations/inputs/update-managed-organization.input"; +import { GetManagedOrganizationsOutput } from "@/modules/organizations/organizations/outputs/get-managed-organizations.output"; import { ManagedOrganizationWithApiKeyOutput, ManagedOrganizationOutput, @@ -304,7 +305,7 @@ describe("Organizations Organizations Endpoints", () => { .set("Authorization", `Bearer ${managerOrgAdminApiKey}`) .expect(200) .then(async (response) => { - const responseBody: ApiSuccessResponse = response.body; + const responseBody: GetManagedOrganizationsOutput = response.body; expect(responseBody.status).toEqual(SUCCESS_STATUS); const responseManagedOrgs = responseBody.data; expect(responseManagedOrgs?.length).toEqual(1); @@ -312,6 +313,14 @@ describe("Organizations Organizations Endpoints", () => { expect(responseManagedOrg?.id).toBeDefined(); expect(responseManagedOrg?.name).toEqual(managedOrg.name); expect(responseManagedOrg?.metadata).toEqual(managedOrg.metadata); + + expect(responseBody.pagination).toBeDefined(); + expect(responseBody.pagination.totalItems).toEqual(1); + expect(responseBody.pagination.remainingItems).toEqual(0); + expect(responseBody.pagination.returnedItems).toEqual(1); + expect(responseBody.pagination.itemsPerPage).toEqual(250); + expect(responseBody.pagination.currentPage).toEqual(1); + expect(responseBody.pagination.totalPages).toEqual(1); }); }); diff --git a/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.ts b/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.ts index 1e2bb3e512..0f811c499b 100644 --- a/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.ts +++ b/apps/api/v2/src/modules/organizations/organizations/organizations-organizations.controller.ts @@ -28,10 +28,12 @@ import { HttpCode, HttpStatus, Delete, + Query, } from "@nestjs/common"; import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger"; import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import { SkipTakePagination } from "@calcom/platform-types"; const SCALE = "SCALE"; @@ -99,14 +101,15 @@ export class OrganizationsOrganizationsController { "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.", }) async getOrganizations( - @Param("orgId", ParseIntPipe) managerOrganizationId: number + @Param("orgId", ParseIntPipe) managerOrganizationId: number, + @Query() queryPagination: SkipTakePagination ): Promise { - const organizations = await this.managedOrganizationsService.getManagedOrganizations( - managerOrganizationId - ); + const { organizations, pagination: responsePagination } = + await this.managedOrganizationsService.getManagedOrganizations(managerOrganizationId, queryPagination); return { status: SUCCESS_STATUS, data: organizations, + pagination: responsePagination, }; } diff --git a/apps/api/v2/src/modules/organizations/organizations/outputs/get-managed-organizations.output.ts b/apps/api/v2/src/modules/organizations/organizations/outputs/get-managed-organizations.output.ts index e063bcbe5d..cf52f8d674 100644 --- a/apps/api/v2/src/modules/organizations/organizations/outputs/get-managed-organizations.output.ts +++ b/apps/api/v2/src/modules/organizations/organizations/outputs/get-managed-organizations.output.ts @@ -4,6 +4,7 @@ import { Expose, Type } from "class-transformer"; import { IsEnum, ValidateNested } from "class-validator"; import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants"; +import { PaginationMetaDto } from "@calcom/platform-types"; export class GetManagedOrganizationsOutput { @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) @@ -17,4 +18,9 @@ export class GetManagedOrganizationsOutput { @ValidateNested({ each: true }) @Type(() => ManagedOrganizationOutput) data!: ManagedOrganizationOutput[]; + + @ApiProperty({ type: () => PaginationMetaDto }) + @Type(() => PaginationMetaDto) + @ValidateNested() + pagination!: PaginationMetaDto; } diff --git a/apps/api/v2/src/modules/organizations/organizations/services/managed-organizations.service.ts b/apps/api/v2/src/modules/organizations/organizations/services/managed-organizations.service.ts index f4a917c3b0..cd81868151 100644 --- a/apps/api/v2/src/modules/organizations/organizations/services/managed-organizations.service.ts +++ b/apps/api/v2/src/modules/organizations/organizations/services/managed-organizations.service.ts @@ -1,3 +1,4 @@ +import { getPagination } from "@/lib/pagination/pagination"; import { ApiKeysService } from "@/modules/api-keys/services/api-keys.service"; import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy"; import { ManagedOrganizationsBillingService } from "@/modules/billing/services/managed-organizations.billing.service"; @@ -11,6 +12,7 @@ import { ProfilesRepository } from "@/modules/profiles/profiles.repository"; import { ForbiddenException, Injectable, NotFoundException } from "@nestjs/common"; import { slugify } from "@calcom/platform-libraries"; +import { SkipTakePagination } from "@calcom/platform-types"; @Injectable() export class ManagedOrganizationsService { @@ -99,18 +101,22 @@ export class ManagedOrganizationsService { return this.managedOrganizationsOutputService.getOutputManagedOrganization(organization); } - async getManagedOrganizations(managerOrganizationId: number) { - const managedOrganizations = await this.managedOrganizationsRepository.getByManagerOrganizationId( - managerOrganizationId - ); - const managedOrganizationsIds = managedOrganizations.map( - (managedOrganization) => managedOrganization.managedOrganizationId - ); + async getManagedOrganizations(managerOrganizationId: number, pagination: SkipTakePagination) { + const { items: managedOrganizations, totalItems } = + await this.managedOrganizationsRepository.getByManagerOrganizationIdPaginated( + managerOrganizationId, + pagination + ); - const organizations = await this.organizationsRepository.findByIds(managedOrganizationsIds); - return organizations.map((organization) => - this.managedOrganizationsOutputService.getOutputManagedOrganization(organization) - ); + return { + organizations: managedOrganizations.map((managedOrganization) => + this.managedOrganizationsOutputService.getOutputManagedOrganization(managedOrganization) + ), + pagination: getPagination({ + ...pagination, + totalCount: totalItems, + }), + }; } async updateManagedOrganization(managedOrganizationId: number, body: UpdateOrganizationInput) { diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 6c614762a7..7e36b9b43f 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -446,9 +446,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -456,9 +459,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -654,9 +659,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -664,9 +672,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -1938,9 +1948,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -1948,9 +1961,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -2639,9 +2654,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -2649,9 +2667,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -2717,9 +2737,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -2727,9 +2750,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -2861,9 +2886,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -2871,9 +2899,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -4195,9 +4225,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -4205,9 +4238,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -4281,9 +4316,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -4291,9 +4329,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -5695,9 +5735,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -5705,9 +5748,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -5971,9 +6016,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -5981,9 +6029,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -6408,9 +6458,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -6418,9 +6471,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -8771,9 +8826,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -8781,9 +8839,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -9042,6 +9102,31 @@ "schema": { "type": "number" } + }, + { + "name": "take", + "required": false, + "in": "query", + "description": "Maximum number of items to return", + "example": 25, + "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, + "type": "number" + } + }, + { + "name": "skip", + "required": false, + "in": "query", + "description": "Number of items to skip", + "example": 0, + "schema": { + "minimum": 0, + "default": 0, + "type": "number" + } } ], "responses": { @@ -9700,9 +9785,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -9710,9 +9798,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -9760,9 +9850,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -9770,9 +9863,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -11603,9 +11698,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -11613,9 +11711,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -11992,9 +12092,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -12002,9 +12105,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -12052,9 +12157,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -12062,9 +12170,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -12372,9 +12482,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -12382,9 +12495,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -12424,9 +12539,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -12434,9 +12552,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -12606,9 +12726,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -12616,9 +12739,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -21998,6 +22123,66 @@ "data" ] }, + "PaginationMetaDto": { + "type": "object", + "properties": { + "totalItems": { + "type": "number", + "description": "The total number of items available across all pages, matching the query criteria.", + "example": 123, + "minimum": 0 + }, + "remainingItems": { + "type": "number", + "description": "The number of items remaining to be fetched *after* the current page. Calculated as: `totalItems - (skip + itemsPerPage)`.", + "example": 103, + "minimum": 0 + }, + "returnedItems": { + "type": "number", + "description": "The number of items returned in the current page.", + "example": 10 + }, + "itemsPerPage": { + "type": "number", + "description": "The maximum number of items requested per page.", + "example": 10, + "minimum": 1 + }, + "currentPage": { + "type": "number", + "description": "The current page number being returned.", + "example": 2, + "minimum": 1 + }, + "totalPages": { + "type": "number", + "description": "The total number of pages available.", + "example": 13, + "minimum": 0 + }, + "hasNextPage": { + "type": "boolean", + "description": "Indicates if there is a subsequent page available after the current one.", + "example": true + }, + "hasPreviousPage": { + "type": "boolean", + "description": "Indicates if there is a preceding page available before the current one.", + "example": true + } + }, + "required": [ + "totalItems", + "remainingItems", + "returnedItems", + "itemsPerPage", + "currentPage", + "totalPages", + "hasNextPage", + "hasPreviousPage" + ] + }, "GetManagedOrganizationsOutput": { "type": "object", "properties": { @@ -22014,11 +22199,15 @@ "items": { "$ref": "#/components/schemas/ManagedOrganizationOutput" } + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMetaDto" } }, "required": [ "status", - "data" + "data", + "pagination" ] }, "UpdateOrganizationInput": { @@ -24205,60 +24394,6 @@ "data" ] }, - "PaginationMetaDto": { - "type": "object", - "properties": { - "totalItems": { - "type": "number", - "minimum": 0, - "description": "The total number of items available across all pages, matching the query criteria.", - "example": 123 - }, - "remainingItems": { - "type": "number", - "minimum": 0, - "description": "The number of items remaining to be fetched *after* the current page. Calculated as: `totalItems - (skip + itemsPerPage)`.", - "example": 103 - }, - "itemsPerPage": { - "type": "number", - "minimum": 1, - "description": "The maximum number of items requested per page.", - "example": 10 - }, - "currentPage": { - "type": "number", - "minimum": 1, - "description": "The current page number being returned.", - "example": 2 - }, - "totalPages": { - "type": "number", - "minimum": 0, - "description": "The total number of pages available.", - "example": 13 - }, - "hasNextPage": { - "type": "boolean", - "description": "Indicates if there is a subsequent page available after the current one.", - "example": true - }, - "hasPreviousPage": { - "type": "boolean", - "description": "Indicates if there is a preceding page available before the current one.", - "example": true - } - }, - "required": [ - "totalItems", - "remainingItems", - "itemsPerPage", - "currentPage", - "totalPages", - "hasNextPage", - "hasPreviousPage" - ] - }, "GetBookingsOutput_2024_08_13": { "type": "object", "properties": { diff --git a/docs/api-reference/v2/openapi.json b/docs/api-reference/v2/openapi.json index b18c8df952..a005beaa7d 100644 --- a/docs/api-reference/v2/openapi.json +++ b/docs/api-reference/v2/openapi.json @@ -430,9 +430,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -440,9 +443,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -628,9 +633,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -638,9 +646,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -1860,9 +1870,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -1870,9 +1883,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -2533,9 +2548,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -2543,9 +2561,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -2609,9 +2629,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -2619,9 +2642,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -2749,9 +2774,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -2759,9 +2787,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -4011,9 +4041,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -4021,9 +4054,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -4095,9 +4130,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -4105,9 +4143,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -5447,9 +5487,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -5457,9 +5500,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -5709,9 +5754,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -5719,9 +5767,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -6128,9 +6178,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -6138,9 +6191,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -8358,9 +8413,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -8368,9 +8426,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -8617,6 +8677,31 @@ "schema": { "type": "number" } + }, + { + "name": "take", + "required": false, + "in": "query", + "description": "Maximum number of items to return", + "example": 25, + "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, + "type": "number" + } + }, + { + "name": "skip", + "required": false, + "in": "query", + "description": "Number of items to skip", + "example": 0, + "schema": { + "minimum": 0, + "default": 0, + "type": "number" + } } ], "responses": { @@ -9245,9 +9330,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -9255,9 +9343,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -9303,9 +9393,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -9313,9 +9406,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -11073,9 +11168,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -11083,9 +11181,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -11446,9 +11546,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -11456,9 +11559,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -11504,9 +11609,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -11514,9 +11622,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -11810,9 +11920,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -11820,9 +11933,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -11860,9 +11975,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -11870,9 +11988,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } }, @@ -12034,9 +12154,12 @@ "name": "take", "required": false, "in": "query", - "description": "The number of items to return", - "example": 10, + "description": "Maximum number of items to return", + "example": 25, "schema": { + "minimum": 1, + "maximum": 250, + "default": 250, "type": "number" } }, @@ -12044,9 +12167,11 @@ "name": "skip", "required": false, "in": "query", - "description": "The number of items to skip", + "description": "Number of items to skip", "example": 0, "schema": { + "minimum": 0, + "default": 0, "type": "number" } } @@ -20158,6 +20283,66 @@ }, "required": ["status", "data"] }, + "PaginationMetaDto": { + "type": "object", + "properties": { + "totalItems": { + "type": "number", + "description": "The total number of items available across all pages, matching the query criteria.", + "example": 123, + "minimum": 0 + }, + "remainingItems": { + "type": "number", + "description": "The number of items remaining to be fetched *after* the current page. Calculated as: `totalItems - (skip + itemsPerPage)`.", + "example": 103, + "minimum": 0 + }, + "returnedItems": { + "type": "number", + "description": "The number of items returned in the current page.", + "example": 10 + }, + "itemsPerPage": { + "type": "number", + "description": "The maximum number of items requested per page.", + "example": 10, + "minimum": 1 + }, + "currentPage": { + "type": "number", + "description": "The current page number being returned.", + "example": 2, + "minimum": 1 + }, + "totalPages": { + "type": "number", + "description": "The total number of pages available.", + "example": 13, + "minimum": 0 + }, + "hasNextPage": { + "type": "boolean", + "description": "Indicates if there is a subsequent page available after the current one.", + "example": true + }, + "hasPreviousPage": { + "type": "boolean", + "description": "Indicates if there is a preceding page available before the current one.", + "example": true + } + }, + "required": [ + "totalItems", + "remainingItems", + "returnedItems", + "itemsPerPage", + "currentPage", + "totalPages", + "hasNextPage", + "hasPreviousPage" + ] + }, "GetManagedOrganizationsOutput": { "type": "object", "properties": { @@ -20171,9 +20356,12 @@ "items": { "$ref": "#/components/schemas/ManagedOrganizationOutput" } + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMetaDto" } }, - "required": ["status", "data"] + "required": ["status", "data", "pagination"] }, "UpdateOrganizationInput": { "type": "object", @@ -22189,60 +22377,6 @@ }, "required": ["status", "data"] }, - "PaginationMetaDto": { - "type": "object", - "properties": { - "totalItems": { - "type": "number", - "minimum": 0, - "description": "The total number of items available across all pages, matching the query criteria.", - "example": 123 - }, - "remainingItems": { - "type": "number", - "minimum": 0, - "description": "The number of items remaining to be fetched *after* the current page. Calculated as: `totalItems - (skip + itemsPerPage)`.", - "example": 103 - }, - "itemsPerPage": { - "type": "number", - "minimum": 1, - "description": "The maximum number of items requested per page.", - "example": 10 - }, - "currentPage": { - "type": "number", - "minimum": 1, - "description": "The current page number being returned.", - "example": 2 - }, - "totalPages": { - "type": "number", - "minimum": 0, - "description": "The total number of pages available.", - "example": 13 - }, - "hasNextPage": { - "type": "boolean", - "description": "Indicates if there is a subsequent page available after the current one.", - "example": true - }, - "hasPreviousPage": { - "type": "boolean", - "description": "Indicates if there is a preceding page available before the current one.", - "example": true - } - }, - "required": [ - "totalItems", - "remainingItems", - "itemsPerPage", - "currentPage", - "totalPages", - "hasNextPage", - "hasPreviousPage" - ] - }, "GetBookingsOutput_2024_08_13": { "type": "object", "properties": { diff --git a/packages/platform/types/api.ts b/packages/platform/types/api.ts index c50dccd49e..daea8e54cf 100644 --- a/packages/platform/types/api.ts +++ b/packages/platform/types/api.ts @@ -1,6 +1,4 @@ -import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { Transform } from "class-transformer"; -import { IsNumber, Min, Max, IsOptional } from "class-validator"; +import { ApiProperty } from "@nestjs/swagger"; import { IsEnum } from "class-validator"; import type { Response as BaseResponse } from "express"; @@ -38,41 +36,6 @@ export type ApiResponse = T extends undefined export type ApiResponseMaybeRedirect = ApiResponse | ApiRedirectResponseType; -export class Pagination { - @ApiPropertyOptional({ description: "The number of items to return", example: 10 }) - @Transform(({ value }: { value: string }) => value && parseInt(value)) - @IsNumber() - @Min(1) - @Max(250) - @IsOptional() - limit?: number; - - @ApiPropertyOptional({ description: "The number of items to skip", example: 0 }) - @Transform(({ value }: { value: string }) => value && parseInt(value)) - @IsNumber() - @Min(0) - @Max(250) - @IsOptional() - offset?: number; -} - -export class SkipTakePagination { - @ApiProperty({ required: false, description: "The number of items to return", example: 10 }) - @Transform(({ value }: { value: string }) => value && parseInt(value)) - @IsNumber() - @Min(1) - @Max(250) - @IsOptional() - take?: number; - - @ApiProperty({ required: false, description: "The number of items to skip", example: 0 }) - @Transform(({ value }: { value: string }) => value && parseInt(value)) - @IsNumber() - @Min(0) - @IsOptional() - skip?: number; -} - export class ApiResponseWithoutData { @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) diff --git a/packages/platform/types/bookings/2024-08-13/outputs/get-bookings.output.ts b/packages/platform/types/bookings/2024-08-13/outputs/get-bookings.output.ts index dabc39cf33..3004967afe 100644 --- a/packages/platform/types/bookings/2024-08-13/outputs/get-bookings.output.ts +++ b/packages/platform/types/bookings/2024-08-13/outputs/get-bookings.output.ts @@ -1,77 +1,16 @@ import { ApiProperty, ApiExtraModels, getSchemaPath } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsBoolean, IsEnum, IsInt, Min, ValidateNested } from "class-validator"; +import { IsEnum, ValidateNested } from "class-validator"; import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants"; import { BookingOutput_2024_08_13, GetRecurringSeatedBookingOutput_2024_08_13, GetSeatedBookingOutput_2024_08_13, + PaginationMetaDto, RecurringBookingOutput_2024_08_13, } from "@calcom/platform-types"; -export class PaginationMetaDto { - @ApiProperty({ - description: "The total number of items available across all pages, matching the query criteria.", - example: 123, - minimum: 0, - }) - @IsInt() - @Min(0) - totalItems!: number; - - @ApiProperty({ - description: - "The number of items remaining to be fetched *after* the current page. Calculated as: `totalItems - (skip + itemsPerPage)`.", - example: 103, // e.g., if totalItems=123, skip=10, itemsPerPage=10 -> 123 - (10 + 10) = 103 - minimum: 0, - }) - @IsInt() - @Min(0) - remainingItems!: number; - - @ApiProperty({ - description: "The maximum number of items requested per page.", - example: 10, - minimum: 1, - }) - @IsInt() - @Min(1) // Typically, you request at least 1 item per page - itemsPerPage!: number; - - @ApiProperty({ - description: "The current page number being returned.", - example: 2, // e.g., if skip=10, itemsPerPage=10 -> page 2 - minimum: 1, - }) - @IsInt() - @Min(1) // Page numbers usually start from 1 - currentPage!: number; - - @ApiProperty({ - description: "The total number of pages available.", - example: 13, // e.g., if totalItems=123, itemsPerPage=10 -> 13 pages - minimum: 0, // Can be 0 if totalItems is 0 - }) - @IsInt() - @Min(0) - totalPages!: number; - - @ApiProperty({ - description: "Indicates if there is a subsequent page available after the current one.", - example: true, - }) - @IsBoolean() - hasNextPage!: boolean; - - @ApiProperty({ - description: "Indicates if there is a preceding page available before the current one.", - example: true, - }) - @IsBoolean() - hasPreviousPage!: boolean; -} - @ApiExtraModels( BookingOutput_2024_08_13, RecurringBookingOutput_2024_08_13, diff --git a/packages/platform/types/index.ts b/packages/platform/types/index.ts index 0a4590491a..eede061db0 100644 --- a/packages/platform/types/index.ts +++ b/packages/platform/types/index.ts @@ -13,3 +13,5 @@ export * from "./embed"; export * from "./oauth-clients"; export * from "./routing-forms"; export * from "./me"; +export * from "./pagination/pagination.input"; +export * from "./pagination/pagination.output"; diff --git a/packages/platform/types/pagination/pagination.input.ts b/packages/platform/types/pagination/pagination.input.ts new file mode 100644 index 0000000000..be2853e64d --- /dev/null +++ b/packages/platform/types/pagination/pagination.input.ts @@ -0,0 +1,52 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { Transform } from "class-transformer"; +import { IsNumber, Min, Max, IsOptional } from "class-validator"; + +export class Pagination { + @ApiPropertyOptional({ description: "The number of items to return", example: 10 }) + @Transform(({ value }: { value: string }) => value && parseInt(value)) + @IsNumber() + @Min(1) + @Max(250) + @IsOptional() + limit?: number; + + @ApiPropertyOptional({ description: "The number of items to skip", example: 0 }) + @Transform(({ value }: { value: string }) => value && parseInt(value)) + @IsNumber() + @Min(0) + @Max(250) + @IsOptional() + offset?: number; +} + +const DEFAULT_TAKE = 250; +const DEFAULT_SKIP = 0; + +export class SkipTakePagination { + @ApiPropertyOptional({ + description: "Maximum number of items to return", + example: 25, + default: DEFAULT_TAKE, + minimum: 1, + maximum: DEFAULT_TAKE, + }) + @Transform(({ value }: { value: string }) => (value ? parseInt(value) : DEFAULT_TAKE)) + @IsNumber() + @Min(1) + @Max(DEFAULT_TAKE) + @IsOptional() + take: number = DEFAULT_TAKE; + + @ApiPropertyOptional({ + description: "Number of items to skip", + example: 0, + default: DEFAULT_SKIP, + minimum: 0, + }) + @Transform(({ value }: { value: string }) => (value ? parseInt(value) : DEFAULT_SKIP)) + @IsNumber() + @Min(0) + @IsOptional() + skip: number = DEFAULT_SKIP; +} diff --git a/packages/platform/types/pagination/pagination.output.ts b/packages/platform/types/pagination/pagination.output.ts new file mode 100644 index 0000000000..dfa9832601 --- /dev/null +++ b/packages/platform/types/pagination/pagination.output.ts @@ -0,0 +1,72 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsBoolean, IsInt, Min } from "class-validator"; + +export class PaginationMetaDto { + @ApiProperty({ + description: "The total number of items available across all pages, matching the query criteria.", + example: 123, + minimum: 0, + }) + @IsInt() + @Min(0) + totalItems!: number; + + @ApiProperty({ + description: + "The number of items remaining to be fetched *after* the current page. Calculated as: `totalItems - (skip + itemsPerPage)`.", + example: 103, // e.g., if totalItems=123, skip=10, itemsPerPage=10 -> 123 - (10 + 10) = 103 + minimum: 0, + }) + @IsInt() + @Min(0) + remainingItems!: number; + + @ApiProperty({ + description: "The number of items returned in the current page.", + example: 10, + }) + @IsInt() + @Min(0) + returnedItems!: number; + + @ApiProperty({ + description: "The maximum number of items requested per page.", + example: 10, + minimum: 1, + }) + @IsInt() + @Min(1) // Typically, you request at least 1 item per page + itemsPerPage!: number; + + @ApiProperty({ + description: "The current page number being returned.", + example: 2, // e.g., if skip=10, itemsPerPage=10 -> page 2 + minimum: 1, + }) + @IsInt() + @Min(1) // Page numbers usually start from 1 + currentPage!: number; + + @ApiProperty({ + description: "The total number of pages available.", + example: 13, // e.g., if totalItems=123, itemsPerPage=10 -> 13 pages + minimum: 0, // Can be 0 if totalItems is 0 + }) + @IsInt() + @Min(0) + totalPages!: number; + + @ApiProperty({ + description: "Indicates if there is a subsequent page available after the current one.", + example: true, + }) + @IsBoolean() + hasNextPage!: boolean; + + @ApiProperty({ + description: "Indicates if there is a preceding page available before the current one.", + example: true, + }) + @IsBoolean() + hasPreviousPage!: boolean; +}