diff --git a/src/api/base.ts b/src/api/base.ts index 5e94cdd..fe351b2 100644 --- a/src/api/base.ts +++ b/src/api/base.ts @@ -47,6 +47,30 @@ export interface IPagination { perPage?: number; } +interface IBasePaginationExtended { + /** + * the page number to fetch (default: 1) + */ + page?: number; + /** + * how many items to fetch per page (default: 100) + */ + perPage?: number; + /** + * the total number of items + */ + includeTotalCount?: boolean; +} + +type IPaginationForceIncludeTotal = IBasePaginationExtended & { includeTotalCount: true }; +export type IPaginationExtended = IBasePaginationExtended | IPaginationForceIncludeTotal; + +export type ReturnPaginationType< + T extends IPaginationExtended, + Y, + Z, +> = T extends IPaginationForceIncludeTotal ? Y : Z; + export abstract class BasePermitApi { protected openapiClientConfig: Configuration; private scopeApi: APIKeysApi; diff --git a/src/api/deprecated.ts b/src/api/deprecated.ts index 36f4d57..819c982 100644 --- a/src/api/deprecated.ts +++ b/src/api/deprecated.ts @@ -179,9 +179,9 @@ export class DeprecatedApiClient extends BasePermitApi implements IDeprecatedPer await this.ensureContext(ApiContextLevel.ENVIRONMENT); try { - const response = await this._roles.listRoles({ + const response = (await this._roles.listRoles({ ...this.config.apiContext.environmentContext, - }); + })) as AxiosResponse; this.logger.debug(`[${response.status}] permit.api.listRoles()`); return response.data; diff --git a/src/api/resources.ts b/src/api/resources.ts index f6fc6f7..1d5c9eb 100644 --- a/src/api/resources.ts +++ b/src/api/resources.ts @@ -3,6 +3,7 @@ import { Logger } from 'pino'; import { IPermitConfig } from '../config'; import { ResourcesApi as AutogenResourcesApi, + PaginatedResultResourceRead, ResourceCreate, ResourceRead, ResourceReplace, @@ -10,25 +11,24 @@ import { } from '../openapi'; import { BASE_PATH } from '../openapi/base'; -import { BasePermitApi, IPagination } from './base'; +import { BasePermitApi, IPaginationExtended, ReturnPaginationType } from './base'; import { ApiContextLevel, ApiKeyLevel } from './context'; export { ResourceCreate, ResourceRead, ResourceReplace, ResourceUpdate } from '../openapi'; -export interface IListResourceUsers extends IPagination { - resourceKey: string; -} - export interface IResourcesApi { /** * Retrieves a list of resources. * - * @param pagination The pagination options, @see {@link IPagination} + * @param pagination The pagination options, @see {@link IPaginationExtended} * @returns A promise that resolves to an array of resources. * @throws {@link PermitApiError} If the API returns an error HTTP status code. * @throws {@link PermitContextError} If the configured {@link ApiContext} does not match the required endpoint context. */ - list(pagination?: IPagination): Promise; + list(): Promise; + list( + pagination?: T, + ): Promise>; /** * Retrieves a resource by its key. @@ -128,13 +128,19 @@ export class ResourcesApi extends BasePermitApi implements IResourcesApi { /** * Retrieves a list of resources. * - * @param pagination The pagination options, @see {@link IPagination} + * @param pagination The pagination options, @see {@link IPaginationExtended} * @returns A promise that resolves to an array of resources. * @throws {@link PermitApiError} If the API returns an error HTTP status code. * @throws {@link PermitContextError} If the configured {@link ApiContext} does not match the required endpoint context. */ - public async list(pagination?: IPagination): Promise { - const { page = 1, perPage = 100 } = pagination ?? {}; + public async list(): Promise; + public async list( + pagination?: T, + ): Promise>; + public async list( + pagination?: IPaginationExtended, + ): Promise { + const { page = 1, perPage = 100, includeTotalCount } = pagination ?? {}; await this.ensureAccessLevel(ApiKeyLevel.ENVIRONMENT_LEVEL_API_KEY); await this.ensureContext(ApiContextLevel.ENVIRONMENT); try { @@ -143,6 +149,7 @@ export class ResourcesApi extends BasePermitApi implements IResourcesApi { ...this.config.apiContext.environmentContext, page, perPage, + includeTotalCount, }) ).data; } catch (err) { diff --git a/src/api/roles.ts b/src/api/roles.ts index ad9853e..211c5a6 100644 --- a/src/api/roles.ts +++ b/src/api/roles.ts @@ -1,10 +1,16 @@ import { Logger } from 'pino'; import { IPermitConfig } from '../config'; -import { RolesApi as AutogenRolesApi, RoleCreate, RoleRead, RoleUpdate } from '../openapi'; +import { + RolesApi as AutogenRolesApi, + PaginatedResultRoleRead, + RoleCreate, + RoleRead, + RoleUpdate, +} from '../openapi'; import { BASE_PATH } from '../openapi/base'; -import { BasePermitApi, IPagination } from './base'; +import { BasePermitApi, IPaginationExtended, ReturnPaginationType } from './base'; import { ApiContextLevel, ApiKeyLevel } from './context'; export { RoleCreate, RoleRead, RoleUpdate } from '../openapi'; @@ -13,12 +19,15 @@ export interface IRolesApi { /** * Retrieves a list of roles. * - * @param pagination The pagination options, @see {@link IPagination} + * @param pagination The pagination options, @see {@link IPaginationExtended} * @returns A promise that resolves to an array of roles. * @throws {@link PermitApiError} If the API returns an error HTTP status code. * @throws {@link PermitContextError} If the configured {@link ApiContext} does not match the required endpoint context. */ - list(pagination?: IPagination): Promise; + list(): Promise; + list( + pagination?: T, + ): Promise>; /** * Retrieves a role by its key. @@ -127,13 +136,19 @@ export class RolesApi extends BasePermitApi implements IRolesApi { /** * Retrieves a list of roles. * - * @param pagination The pagination options, @see {@link IPagination} + * @param pagination The pagination options, @see {@link IPaginationExtended} * @returns A promise that resolves to an array of roles. * @throws {@link PermitApiError} If the API returns an error HTTP status code. * @throws {@link PermitContextError} If the configured {@link ApiContext} does not match the required endpoint context. */ - public async list(pagination?: IPagination): Promise { - const { page = 1, perPage = 100 } = pagination ?? {}; + public async list(): Promise; + public async list( + pagination?: T, + ): Promise>; + public async list( + pagination?: IPaginationExtended, + ): Promise { + const { page = 1, perPage = 100, includeTotalCount } = pagination ?? {}; await this.ensureAccessLevel(ApiKeyLevel.ENVIRONMENT_LEVEL_API_KEY); await this.ensureContext(ApiContextLevel.ENVIRONMENT); try { @@ -142,6 +157,7 @@ export class RolesApi extends BasePermitApi implements IRolesApi { ...this.config.apiContext.environmentContext, page, perPage, + includeTotalCount, }) ).data; } catch (err) { diff --git a/src/openapi/api/resources-api.ts b/src/openapi/api/resources-api.ts index 38daa9c..38b2ed2 100644 --- a/src/openapi/api/resources-api.ts +++ b/src/openapi/api/resources-api.ts @@ -31,7 +31,7 @@ import { // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base'; // @ts-ignore -import { HTTPValidationError } from '../types'; +import { HTTPValidationError, PaginatedResultResourceRead } from '../types'; // @ts-ignore import { ResourceCreate } from '../types'; // @ts-ignore @@ -219,6 +219,7 @@ export const ResourcesApiAxiosParamCreator = function (configuration?: Configura * @param {boolean} [includeBuiltIn] Whether to include or exclude built-in resources, default is False * @param {number} [page] Page number of the results to fetch, starting at 1. * @param {number} [perPage] The number of results per page (max 100). + * @param {includeTotalCount} [includeTotalCount] Include the total count of resources in the response, default is False * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -228,6 +229,7 @@ export const ResourcesApiAxiosParamCreator = function (configuration?: Configura includeBuiltIn?: boolean, page?: number, perPage?: number, + includeTotalCount?: boolean, options: AxiosRequestConfig = {}, ): Promise => { // verify required parameter 'projId' is not null or undefined @@ -264,6 +266,10 @@ export const ResourcesApiAxiosParamCreator = function (configuration?: Configura localVarQueryParameter['per_page'] = perPage; } + if (includeTotalCount !== undefined) { + localVarQueryParameter['include_total_count'] = includeTotalCount; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = { @@ -492,6 +498,7 @@ export const ResourcesApiFp = function (configuration?: Configuration) { * @param {boolean} [includeBuiltIn] Whether to include or exclude built-in resources, default is False * @param {number} [page] Page number of the results to fetch, starting at 1. * @param {number} [perPage] The number of results per page (max 100). + * @param {includeTotalCount} [includeTotalCount] Include the total count of resources in the response, default is False * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -501,14 +508,21 @@ export const ResourcesApiFp = function (configuration?: Configuration) { includeBuiltIn?: boolean, page?: number, perPage?: number, + includeTotalCount?: boolean, options?: AxiosRequestConfig, - ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + ): Promise< + ( + axios?: AxiosInstance, + basePath?: string, + ) => AxiosPromise | PaginatedResultResourceRead> + > { const localVarAxiosArgs = await localVarAxiosParamCreator.listResources( projId, envId, includeBuiltIn, page, perPage, + includeTotalCount, options, ); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); @@ -644,6 +658,7 @@ export const ResourcesApiFactory = function ( * @param {boolean} [includeBuiltIn] Whether to include or exclude built-in resources, default is False * @param {number} [page] Page number of the results to fetch, starting at 1. * @param {number} [perPage] The number of results per page (max 100). + * @param {includeTotalCount} [includeTotalCount] Include the total count of resources in the response, default is False * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -653,10 +668,11 @@ export const ResourcesApiFactory = function ( includeBuiltIn?: boolean, page?: number, perPage?: number, + includeTotalCount?: boolean, options?: any, - ): AxiosPromise> { + ): AxiosPromise | PaginatedResultResourceRead> { return localVarFp - .listResources(projId, envId, includeBuiltIn, page, perPage, options) + .listResources(projId, envId, includeBuiltIn, page, perPage, includeTotalCount, options) .then((request) => request(axios, basePath)); }, /** @@ -828,6 +844,14 @@ export interface ResourcesApiListResourcesRequest { * @memberof ResourcesApiListResources */ readonly perPage?: number; + + /** + * Include total count in response + * @type {boolean} + * @memberof RolesApiListRoles + * @default false + */ + readonly includeTotalCount?: boolean; } /** @@ -992,6 +1016,7 @@ export class ResourcesApi extends BaseAPI { requestParameters.includeBuiltIn, requestParameters.page, requestParameters.perPage, + requestParameters.includeTotalCount, options, ) .then((request) => request(this.axios, this.basePath)); diff --git a/src/openapi/api/roles-api.ts b/src/openapi/api/roles-api.ts index c0c5e04..ccc97df 100644 --- a/src/openapi/api/roles-api.ts +++ b/src/openapi/api/roles-api.ts @@ -31,7 +31,7 @@ import { // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base'; // @ts-ignore -import { AddRolePermissions } from '../types'; +import { AddRolePermissions, PaginatedResultRoleRead } from '../types'; // @ts-ignore import { HTTPValidationError } from '../types'; // @ts-ignore @@ -342,6 +342,7 @@ export const RolesApiAxiosParamCreator = function (configuration?: Configuration * @param {string} envId Either the unique id of the environment, or the URL-friendly key of the environment (i.e: the \"slug\"). * @param {number} [page] Page number of the results to fetch, starting at 1. * @param {number} [perPage] The number of results per page (max 100). + * @param {boolean} [includeTotalCount] Include total count in response (default to false) * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -350,6 +351,7 @@ export const RolesApiAxiosParamCreator = function (configuration?: Configuration envId: string, page?: number, perPage?: number, + includeTotalCount?: boolean, options: AxiosRequestConfig = {}, ): Promise => { // verify required parameter 'projId' is not null or undefined @@ -366,9 +368,13 @@ export const RolesApiAxiosParamCreator = function (configuration?: Configuration baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options }; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; + const localVarRequestOptions: Record = { + method: 'GET', + ...baseOptions, + ...options, + }; + const localVarHeaderParameter: Record = {}; + const localVarQueryParameter: Record = {}; // authentication HTTPBearer required // http bearer authentication required @@ -382,6 +388,10 @@ export const RolesApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['per_page'] = perPage; } + if (includeTotalCount !== undefined) { + localVarQueryParameter['include_total_count'] = includeTotalCount; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = { @@ -723,6 +733,7 @@ export const RolesApiFp = function (configuration?: Configuration) { * @param {string} envId Either the unique id of the environment, or the URL-friendly key of the environment (i.e: the \"slug\"). * @param {number} [page] Page number of the results to fetch, starting at 1. * @param {number} [perPage] The number of results per page (max 100). + * @param {boolean} [includeTotalCount] Include total count in response (default to false) * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -731,13 +742,20 @@ export const RolesApiFp = function (configuration?: Configuration) { envId: string, page?: number, perPage?: number, + includeTotalCount?: boolean, options?: AxiosRequestConfig, - ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + ): Promise< + ( + axios?: AxiosInstance, + basePath?: string, + ) => AxiosPromise | PaginatedResultRoleRead> + > { const localVarAxiosArgs = await localVarAxiosParamCreator.listRoles( projId, envId, page, perPage, + includeTotalCount, options, ); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); @@ -939,7 +957,7 @@ export const RolesApiFactory = function ( page?: number, perPage?: number, options?: any, - ): AxiosPromise> { + ): AxiosPromise | PaginatedResultRoleRead> { return localVarFp .listRoles(projId, envId, page, perPage, options) .then((request) => request(axios, basePath)); @@ -1197,6 +1215,14 @@ export interface RolesApiListRolesRequest { * @memberof RolesApiListRoles */ readonly perPage?: number; + + /** + * Include total count in response + * @type {boolean} + * @memberof RolesApiListRoles + * @default false + */ + readonly includeTotalCount?: boolean; } /** @@ -1424,6 +1450,7 @@ export class RolesApi extends BaseAPI { requestParameters.envId, requestParameters.page, requestParameters.perPage, + requestParameters.includeTotalCount, options, ) .then((request) => request(this.axios, this.basePath)); diff --git a/src/tests/e2e/rbac.e2e.spec.ts b/src/tests/e2e/rbac.e2e.spec.ts index d9d46e1..d4d7826 100644 --- a/src/tests/e2e/rbac.e2e.spec.ts +++ b/src/tests/e2e/rbac.e2e.spec.ts @@ -47,6 +47,7 @@ test('Permission check e2e test', async (t) => { // verify list output const resources = await permit.api.resources.list(); + t.true(Array.isArray(resources)); t.is(resources.length, 1); t.is(resources[0].id, document.id); t.is(resources[0].key, document.key); @@ -54,6 +55,13 @@ test('Permission check e2e test', async (t) => { t.is(resources[0].description, document.description); t.is(resources[0].urn, document.urn); + const resourcesWithTotalCount = await permit.api.resources.list({ includeTotalCount: true }); + t.not(resourcesWithTotalCount, null); + t.not(resourcesWithTotalCount.data, null); + t.is(resourcesWithTotalCount.data.length, 1); + t.is(resourcesWithTotalCount.total_count, 1); + t.is(resourcesWithTotalCount.page_count, 1); + // create admin role const admin = await permit.api.roles.create({ key: 'admin', @@ -84,6 +92,10 @@ test('Permission check e2e test', async (t) => { t.not(viewer.permissions, undefined); t.is(viewer.permissions?.length, 0); + const roles = await permit.api.roles.list(); + t.true(Array.isArray(roles)); + t.is(roles.length, 2); + // assign permissions to roles const assignedViewer = await permit.api.roles.assignPermissions('viewer', ['document:read']); @@ -234,7 +246,12 @@ test('Permission check e2e test', async (t) => { await permit.api.roles.delete('viewer'); await permit.api.resources.delete('document'); t.is((await permit.api.resources.list()).length, 0); - t.is((await permit.api.roles.list()).length, 0); + + const roles = await permit.api.roles.list(); + + t.true(Array.isArray(roles)); + t.is(roles.length, 0); + t.is((await permit.api.tenants.list()).length, 1); // the default tenant t.is((await permit.api.users.list()).data.length, 0); } catch (error) {