From 6acf7669e80d3f62faf58830c87b0c4aa9efd777 Mon Sep 17 00:00:00 2001 From: muntaxir4 Date: Wed, 27 Nov 2024 01:08:52 +0530 Subject: [PATCH] feat(api,schema,api-client): Implement pagination for get all api keys response --- apps/api/src/api-key/api-key.e2e.spec.ts | 30 +++++--- .../src/api-key/service/api-key.service.ts | 20 +++++- packages/api-client/tests/api-key.spec.ts | 16 ++++- packages/api-client/tests/config/setup.ts | 2 +- packages/schema/src/api-key/index.ts | 12 ++-- packages/schema/src/pagination/index.ts | 10 +-- packages/schema/tests/api-key.spec.ts | 71 +++++++++++++------ packages/schema/tests/pagination.spec.ts | 3 +- 8 files changed, 122 insertions(+), 42 deletions(-) diff --git a/apps/api/src/api-key/api-key.e2e.spec.ts b/apps/api/src/api-key/api-key.e2e.spec.ts index ae942be0..008f46de 100644 --- a/apps/api/src/api-key/api-key.e2e.spec.ts +++ b/apps/api/src/api-key/api-key.e2e.spec.ts @@ -285,13 +285,27 @@ describe('Api Key Role Controller Tests', () => { }) expect(response.statusCode).toBe(200) - expect(response.json()[0].id).toBe(apiKey.id) - expect(response.json()[0].name).toBe('Test Key') - expect(response.json()[0].slug).toBe(apiKey.slug) - expect(response.json()[0].authorities).toEqual([ + expect(response.json().items[0].id).toBe(apiKey.id) + expect(response.json().items[0].name).toBe('Test Key') + expect(response.json().items[0].slug).toBe(apiKey.slug) + expect(response.json().items[0].authorities).toEqual([ 'READ_API_KEY', 'CREATE_ENVIRONMENT' ]) + + const metadata = response.json().metadata + expect(metadata.totalCount).toEqual(1) + expect(metadata.links.self).toBe( + `/api-key?page=0&limit=10&sort=name&order=asc&search=` + ) + expect(metadata.links.first).toBe( + `/api-key?page=0&limit=10&sort=name&order=asc&search=` + ) + expect(metadata.links.previous).toEqual(null) + expect(metadata.links.next).toEqual(null) + expect(metadata.links.last).toBe( + `/api-key?page=0&limit=10&sort=name&order=asc&search=` + ) }) it('should be able to get all api keys using the API key', async () => { @@ -304,10 +318,10 @@ describe('Api Key Role Controller Tests', () => { }) expect(response.statusCode).toBe(200) - expect(response.json()[0].id).toBe(apiKey.id) - expect(response.json()[0].name).toBe('Test Key') - expect(response.json()[0].slug).toBe(apiKey.slug) - expect(response.json()[0].authorities).toEqual([ + expect(response.json().items[0].id).toBe(apiKey.id) + expect(response.json().items[0].name).toBe('Test Key') + expect(response.json().items[0].slug).toBe(apiKey.slug) + expect(response.json().items[0].authorities).toEqual([ 'READ_API_KEY', 'CREATE_ENVIRONMENT' ]) diff --git a/apps/api/src/api-key/service/api-key.service.ts b/apps/api/src/api-key/service/api-key.service.ts index 66c6e6dc..6ce27437 100644 --- a/apps/api/src/api-key/service/api-key.service.ts +++ b/apps/api/src/api-key/service/api-key.service.ts @@ -11,6 +11,7 @@ import { ApiKey, User } from '@prisma/client' import generateEntitySlug from '@/common/slug-generator' import { generateApiKey, toSHA256 } from '@/common/cryptography' import { addHoursToDate, limitMaxItemsPerPage } from '@/common/util' +import { paginate } from '@/common/paginate' @Injectable() export class ApiKeyService { @@ -186,7 +187,7 @@ export class ApiKeyService { order: string, search: string ) { - return await this.prisma.apiKey.findMany({ + const items = await this.prisma.apiKey.findMany({ where: { userId: user.id, name: { @@ -200,6 +201,23 @@ export class ApiKeyService { }, select: this.apiKeySelect }) + + const totalCount = await this.prisma.apiKey.count({ + where: { + userId: user.id, + name: { + contains: search + } + } + }) + const metadata = paginate(totalCount, `/api-key`, { + page, + limit: limitMaxItemsPerPage(limit), + sort, + order, + search + }) + return { items, metadata } } /** diff --git a/packages/api-client/tests/api-key.spec.ts b/packages/api-client/tests/api-key.spec.ts index 1c7d1768..2ad832ee 100644 --- a/packages/api-client/tests/api-key.spec.ts +++ b/packages/api-client/tests/api-key.spec.ts @@ -118,7 +118,21 @@ describe('Api-Key Controller Tests', () => { ) expect(response.success).toBe(true) - expect(response.data).toHaveLength(1) + expect(response.data.items).toHaveLength(1) + + const metadata = response.data.metadata + expect(metadata.totalCount).toEqual(1) + expect(metadata.links.self).toBe( + `/api-key?page=0&limit=10&sort=name&order=asc&search=` + ) + expect(metadata.links.first).toBe( + `/api-key?page=0&limit=10&sort=name&order=asc&search=` + ) + expect(metadata.links.previous).toEqual(null) + expect(metadata.links.next).toEqual(null) + expect(metadata.links.last).toBe( + `/api-key?page=0&limit=10&sort=name&order=asc&search=` + ) }) // Get Api Key diff --git a/packages/api-client/tests/config/setup.ts b/packages/api-client/tests/config/setup.ts index 3c7c1cf2..1a4e0f20 100644 --- a/packages/api-client/tests/config/setup.ts +++ b/packages/api-client/tests/config/setup.ts @@ -4,7 +4,7 @@ export default async function teardown() { await executeCommand('docker compose down') await executeCommand('docker compose -f ../../docker-compose-test.yml up -d') await executeCommand('cd ../.. && pnpm build:api') - await executeCommand('cd ../.. && sleep 5 && pnpm db:deploy-migrations', { + await executeCommand('cd ../.. && pnpm db:deploy-migrations', { DATABASE_URL: 'postgresql://prisma:prisma@localhost:5432/tests', PATH: process.env.PATH! }) diff --git a/packages/schema/src/api-key/index.ts b/packages/schema/src/api-key/index.ts index 88fdb179..61f4dd3b 100644 --- a/packages/schema/src/api-key/index.ts +++ b/packages/schema/src/api-key/index.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { expiresAfterEnum, authorityEnum } from '@/enums' -import { PageRequestSchema } from '@/pagination' +import { PageRequestSchema, PageResponseSchema } from '@/pagination' export const ApiKeySchema = z.object({ id: z.string(), @@ -39,10 +39,12 @@ export const DeleteApiKeyResponseSchema = z.void() export const GetApiKeysOfUserRequestSchema = PageRequestSchema -export const GetApiKeysOfUserResponseSchema = ApiKeySchema.omit({ - value: true, - userId: true -}).array() +export const GetApiKeysOfUserResponseSchema = PageResponseSchema( + ApiKeySchema.omit({ + value: true, + userId: true + }) +) export const GetApiKeyRequestSchema = z.object({ apiKeySlug: ApiKeySchema.shape.slug diff --git a/packages/schema/src/pagination/index.ts b/packages/schema/src/pagination/index.ts index 797d9485..89ea0e47 100644 --- a/packages/schema/src/pagination/index.ts +++ b/packages/schema/src/pagination/index.ts @@ -17,11 +17,11 @@ export const PageResponseSchema = (itemSchema: z.ZodType) => pageCount: z.number(), totalCount: z.number(), links: z.object({ - self: z.string(), - first: z.string(), - previous: z.string().nullable(), - next: z.string().nullable(), - last: z.string() + self: z.string().url(), + first: z.string().url(), + previous: z.string().url().nullable(), + next: z.string().url().nullable(), + last: z.string().url() }) }) }) diff --git a/packages/schema/tests/api-key.spec.ts b/packages/schema/tests/api-key.spec.ts index f77b31f7..111f8d58 100644 --- a/packages/schema/tests/api-key.spec.ts +++ b/packages/schema/tests/api-key.spec.ts @@ -218,32 +218,63 @@ describe('API Key Schema Tests', () => { // Tests for GetApiKeysOfUserResponseSchema it('should validate a valid GetApiKeysOfUserResponseSchema', () => { - const result = GetApiKeysOfUserResponseSchema.safeParse([ - { - id: 'apikey123', - name: 'API Key Name', - slug: 'api-key-slug', - expiresAt: '2024-10-10T10:00:00Z', - createdAt: '2024-10-09T10:00:00Z', - updatedAt: '2024-10-09T10:00:00Z', - authorities: ['READ_SECRET', 'READ_VARIABLE'] + const result = GetApiKeysOfUserResponseSchema.safeParse({ + items: [ + { + id: 'apikey123', + name: 'API Key Name', + slug: 'api-key-slug', + expiresAt: '2024-10-10T10:00:00Z', + createdAt: '2024-10-09T10:00:00Z', + updatedAt: '2024-10-09T10:00:00Z', + authorities: ['READ_SECRET', 'READ_VARIABLE'] + } + ], + metadata: { + page: 1, + perPage: 10, + pageCount: 1, + totalCount: 1, + links: { + self: 'http://example.com/page/1', + first: 'http://example.com/page/1', + previous: null, + next: null, + last: 'http://example.com/page/1' + } } - ]) + }) + console.log(result.error?.issues) expect(result.success).toBe(true) }) it('should not validate an invalid GetApiKeysOfUserResponseSchema', () => { - const result = GetApiKeysOfUserResponseSchema.safeParse([ - { - id: 'apikey123', - name: 'API Key Name', - slug: 'api-key-slug', - expiresAt: 'invalid-date', // Should be a valid date string - createdAt: '2024-10-09T10:00:00Z', - updatedAt: '2024-10-09T10:00:00Z', - authorities: ['INVALID_AUTHORITY'] // Invalid authority + const result = GetApiKeysOfUserResponseSchema.safeParse({ + items: [ + { + id: 'apikey123', + name: 'API Key Name', + slug: 'api-key-slug', + expiresAt: 'invalid-date', // Should be a valid date string + createdAt: '2024-10-09T10:00:00Z', + updatedAt: '2024-10-09T10:00:00Z', + authorities: ['INVALID_AUTHORITY'] // Invalid authority + } + ], + metadata: { + page: 1, + perPage: 10, + pageCount: 1, + totalCount: 1, + links: { + self: 'http://example.com/page/1', + first: 'http://example.com/page/1', + previous: null, + next: null, + last: 'http://example.com/page/1' + } } - ]) + }) expect(result.success).toBe(false) expect(result.error?.issues).toHaveLength(2) }) diff --git a/packages/schema/tests/pagination.spec.ts b/packages/schema/tests/pagination.spec.ts index 40dc23c6..82d10f08 100644 --- a/packages/schema/tests/pagination.spec.ts +++ b/packages/schema/tests/pagination.spec.ts @@ -61,7 +61,7 @@ describe('Pagination Schema Tests', () => { pageCount: 1, totalCount: 1, links: { - self: 'http://example.com/page/1', + self: 'not-an-url', // should be a URL first: 'http://example.com/page/1', previous: null, next: null, @@ -71,6 +71,7 @@ describe('Pagination Schema Tests', () => { }) expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(2) }) it('should not validate an invalid PageResponseSchema with missing metadata fields', () => {