mirror of
https://github.com/vernu/textbee.git
synced 2026-09-03 03:29:58 +03:00
docs(api): type pagination counts as integers with encoded bounds
page and limit were declared as number in the spec, which permits fractional values for what are discrete counts. The query params now declare integer schemas with their minimum, maximum, and default, and the pagination meta counters are integers too. Runtime behavior is unchanged; parsePagination already clamped. Also hoists a repeated inline require in the service spec to a top-level import. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+20
-15
@@ -222,19 +222,24 @@
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Messages per page. Default 50, maximum 100.",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
"default": 50
|
||||
},
|
||||
"description": "Messages per page. Default 50, maximum 100."
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Page to return. Default 1. Mutually exclusive with cursor.",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 1
|
||||
},
|
||||
"description": "Page to return. Default 1. Mutually exclusive with cursor."
|
||||
},
|
||||
{
|
||||
"name": "order",
|
||||
@@ -1525,19 +1530,19 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Current page number"
|
||||
},
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Number of items per page"
|
||||
},
|
||||
"total": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Total number of items"
|
||||
},
|
||||
"totalPages": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Total number of pages"
|
||||
},
|
||||
"nextCursor": {
|
||||
@@ -1563,7 +1568,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Number of items per page."
|
||||
},
|
||||
"nextCursor": {
|
||||
@@ -1901,19 +1906,19 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Current page number"
|
||||
},
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Number of items per page"
|
||||
},
|
||||
"total": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Total number of items"
|
||||
},
|
||||
"totalPages": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"description": "Total number of pages"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -507,13 +507,13 @@ export class GatewayController {
|
||||
@ApiQuery({
|
||||
name: 'page',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, default: 1 },
|
||||
description: 'Page to return. Default 1. Mutually exclusive with cursor.',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'limit',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, maximum: 100, default: 50 },
|
||||
description: 'Messages per page. Default 50, maximum 100.',
|
||||
})
|
||||
@ApiQuery({
|
||||
@@ -549,13 +549,13 @@ export class GatewayController {
|
||||
@ApiQuery({
|
||||
name: 'page',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, default: 1 },
|
||||
description: 'Page to return. Default 1.',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'limit',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, maximum: 100, default: 50 },
|
||||
description: 'Messages per page. Default 50, maximum 100.',
|
||||
})
|
||||
@UseGuards(AuthGuard, CanModifyDevice)
|
||||
@@ -587,13 +587,13 @@ export class GatewayController {
|
||||
@ApiQuery({
|
||||
name: 'page',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, default: 1 },
|
||||
description: 'Page to return. Default 1.',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'limit',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, maximum: 100, default: 50 },
|
||||
description: 'Messages per page. Default 50, maximum 100.',
|
||||
})
|
||||
@UseGuards(AuthGuard, CanModifyDevice)
|
||||
@@ -624,13 +624,13 @@ export class GatewayController {
|
||||
@ApiQuery({
|
||||
name: 'page',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, default: 1 },
|
||||
description: 'Page to return. Default 1.',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'limit',
|
||||
required: false,
|
||||
type: Number,
|
||||
schema: { type: 'integer', minimum: 1, maximum: 100, default: 50 },
|
||||
description: 'Messages per page. Default 50, maximum 100.',
|
||||
})
|
||||
@ApiQuery({
|
||||
|
||||
@@ -929,28 +929,28 @@ export class RetrieveSMSDTO {
|
||||
|
||||
export class PaginationMetaDTO {
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
type: 'integer',
|
||||
required: true,
|
||||
description: 'Current page number',
|
||||
})
|
||||
page: number;
|
||||
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
type: 'integer',
|
||||
required: true,
|
||||
description: 'Number of items per page',
|
||||
})
|
||||
limit: number;
|
||||
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
type: 'integer',
|
||||
required: true,
|
||||
description: 'Total number of items',
|
||||
})
|
||||
total: number;
|
||||
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
type: 'integer',
|
||||
required: true,
|
||||
description: 'Total number of pages',
|
||||
})
|
||||
@@ -991,7 +991,7 @@ export class MessageDTO extends RetrieveSMSDTO {
|
||||
}
|
||||
|
||||
export class CursorPaginationMetaDTO {
|
||||
@ApiProperty({ type: Number, description: 'Number of items per page.' })
|
||||
@ApiProperty({ type: 'integer', description: 'Number of items per page.' })
|
||||
limit: number
|
||||
|
||||
@ApiProperty({
|
||||
|
||||
@@ -17,6 +17,7 @@ import * as firebaseAdmin from 'firebase-admin'
|
||||
import { SMSType } from './sms-type.enum'
|
||||
import { WebhookEvent } from '../webhook/webhook-event.enum'
|
||||
import { RegisterDeviceInputDTO, SendBulkSMSInputDTO, SendSMSInputDTO } from './gateway.dto'
|
||||
import { decodeCursor } from './cursor'
|
||||
import { User } from '../users/schemas/user.schema'
|
||||
import { UserRole } from '../users/user-roles.enum'
|
||||
import { BatchResponse } from 'firebase-admin/messaging'
|
||||
@@ -1550,7 +1551,6 @@ describe('GatewayService', () => {
|
||||
}
|
||||
pages++
|
||||
if (!result.meta.nextCursor) break
|
||||
const { decodeCursor } = require('./cursor')
|
||||
cursor = decodeCursor(result.meta.nextCursor)
|
||||
}
|
||||
expect(seen.size).toBe(150)
|
||||
@@ -1567,7 +1567,6 @@ describe('GatewayService', () => {
|
||||
offsetIds.push(...r.data.map((m: any) => String(m._id)))
|
||||
}
|
||||
|
||||
const { decodeCursor } = require('./cursor')
|
||||
const keysetIds: string[] = []
|
||||
let cursor: any = undefined
|
||||
while (true) {
|
||||
@@ -1582,7 +1581,6 @@ describe('GatewayService', () => {
|
||||
|
||||
it('asc keyset walk sees rows inserted behind the head mid-walk', async () => {
|
||||
seed(deviceA, 10, new Date('2026-08-01T00:00:00Z'))
|
||||
const { decodeCursor } = require('./cursor')
|
||||
|
||||
const first = await service.getMessagesForUser(user, { order: 'asc' } as any, 1, 5)
|
||||
// New rows land after the cursor position while we are mid-walk
|
||||
|
||||
Reference in New Issue
Block a user