Skip to content

Commit

Permalink
feat(api,schema,api-client): Implement pagination for get all api key…
Browse files Browse the repository at this point in the history
…s response
  • Loading branch information
muntaxir4 authored and rajdip-b committed Nov 27, 2024
1 parent fee11c6 commit 6acf766
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 42 deletions.
30 changes: 22 additions & 8 deletions apps/api/src/api-key/api-key.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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'
])
Expand Down
20 changes: 19 additions & 1 deletion apps/api/src/api-key/service/api-key.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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: {
Expand All @@ -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 }
}

/**
Expand Down
16 changes: 15 additions & 1 deletion packages/api-client/tests/api-key.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/api-client/tests/config/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!
})
Expand Down
12 changes: 7 additions & 5 deletions packages/schema/src/api-key/index.ts
Original file line number Diff line number Diff line change
@@ -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(),
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions packages/schema/src/pagination/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export const PageResponseSchema = <T>(itemSchema: z.ZodType<T>) =>
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()
})
})
})
Expand Down
71 changes: 51 additions & 20 deletions packages/schema/tests/api-key.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down
3 changes: 2 additions & 1 deletion packages/schema/tests/pagination.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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', () => {
Expand Down

0 comments on commit 6acf766

Please sign in to comment.