From 3710dce4a025da99ffce4b424a51b6c9d07861bb Mon Sep 17 00:00:00 2001 From: typicode Date: Mon, 22 Jan 2024 15:54:16 +0100 Subject: [PATCH] fix: pagination --- src/app.ts | 12 +- src/service.test.ts | 363 +++++++++++++++++++++++--------------------- src/service.ts | 12 +- 3 files changed, 205 insertions(+), 182 deletions(-) diff --git a/src/app.ts b/src/app.ts index 51885a30a..83070fa38 100644 --- a/src/app.ts +++ b/src/app.ts @@ -48,7 +48,17 @@ export function createApp(db: Low, options: AppOptions = {}) { app.get('/:name', (req, res, next) => { const { name = '' } = req.params - res.locals['data'] = service.find(name, req.query) + const query = Object.fromEntries(Object.entries(req.query) + .map(([key, value]) => { + if (['_start', '_end', '_limit', '_page', '_per_page'].includes(key) && typeof value === 'string') { + return [key, parseInt(value)] + } else { + return [key, value] + } + }) + .filter(([_, value]) => !Number.isNaN(value)) + ) + res.locals['data'] = service.find(name, query) next() }) diff --git a/src/service.test.ts b/src/service.test.ts index 3b78cbce7..66453b90b 100644 --- a/src/service.test.ts +++ b/src/service.test.ts @@ -2,7 +2,6 @@ import assert from 'node:assert/strict' import test from 'node:test' import { Low, Memory } from 'lowdb' -import { ParsedUrlQuery } from 'querystring' import { Data, Item, PaginatedItems, Service } from './service.js' @@ -54,14 +53,6 @@ function reset() { }) } -type Test = { - data?: Data - name: string - params?: ParsedUrlQuery - res: Item | Item[] | PaginatedItems | undefined - error?: Error -} - await test('constructor', () => { const defaultData = { posts: [{ id: '1' }, {}], object: {} } satisfies Data const db = new Low(adapter, defaultData) @@ -98,176 +89,196 @@ await test('findById', () => { }) await test('find', async (t) => { - const arr: Test[] = [ - { - name: POSTS, - res: [post1, post2, post3], - }, - { - name: POSTS, - params: { id: post1.id }, - res: [post1], - }, - { - name: POSTS, - params: { id: UNKNOWN_ID }, - res: [], - }, - { - name: POSTS, - params: { views: post1.views.toString() }, - res: [post1], - }, - { - name: POSTS, - params: { 'author.name': post1.author.name }, - res: [post1], - }, - { - name: POSTS, - params: { 'tags[0]': 'foo' }, - res: [post1, post3], - }, - { - name: POSTS, - params: { id: UNKNOWN_ID, views: post1.views.toString() }, - res: [], - }, - { - name: POSTS, - params: { views_ne: post1.views.toString() }, - res: [post2, post3], - }, - { - name: POSTS, - params: { views_lt: (post1.views + 1).toString() }, - res: [post1], - }, - { - name: POSTS, - params: { views_lt: post1.views.toString() }, - res: [], - }, - { - name: POSTS, - params: { views_lte: post1.views.toString() }, - res: [post1], - }, - { - name: POSTS, - params: { views_gt: post1.views.toString() }, - res: [post2, post3], - }, - { - name: POSTS, - params: { views_gt: (post1.views - 1).toString() }, - res: [post1, post2, post3], - }, - { - name: POSTS, - params: { views_gte: post1.views.toString() }, - res: [post1, post2, post3], - }, - { - data: { posts: [post3, post1, post2] }, - name: POSTS, - params: { _sort: 'views' }, - res: [post1, post2, post3], - }, - { - data: { posts: [post3, post1, post2] }, - name: POSTS, - params: { _sort: '-views' }, - res: [post3, post2, post1], - }, - { - data: { posts: [post3, post1, post2] }, - name: POSTS, - params: { _sort: '-views,id' }, - res: [post3, post2, post1], - }, - { - name: POSTS, - params: { _start: '0', _end: '2' }, - res: [post1, post2], - }, - { - name: POSTS, - params: { _start: '1', _end: '3' }, - res: [post2, post3], - }, - { - name: POSTS, - params: { _start: '0', _limit: '2' }, - res: [post1, post2], - }, - { - name: POSTS, - params: { _start: '1', _limit: '2' }, - res: [post2, post3], - }, - { - name: POSTS, - params: { _page: '1', _per_page: '2' }, - res: { - first: 1, - last: 2, - prev: null, - next: 2, - pages: 2, - items, - data: [post1, post2], + const arr: { + data?: Data + name: string + params?: Parameters[1] + res: Item | Item[] | PaginatedItems | undefined + error?: Error + }[] = [ + { + name: POSTS, + res: [post1, post2, post3], + }, + { + name: POSTS, + params: { id: post1.id }, + res: [post1], + }, + { + name: POSTS, + params: { id: UNKNOWN_ID }, + res: [], + }, + { + name: POSTS, + params: { views: post1.views.toString() }, + res: [post1], + }, + { + name: POSTS, + params: { 'author.name': post1.author.name }, + res: [post1], + }, + { + name: POSTS, + params: { 'tags[0]': 'foo' }, + res: [post1, post3], + }, + { + name: POSTS, + params: { id: UNKNOWN_ID, views: post1.views.toString() }, + res: [], + }, + { + name: POSTS, + params: { views_ne: post1.views.toString() }, + res: [post2, post3], + }, + { + name: POSTS, + params: { views_lt: (post1.views + 1).toString() }, + res: [post1], + }, + { + name: POSTS, + params: { views_lt: post1.views.toString() }, + res: [], + }, + { + name: POSTS, + params: { views_lte: post1.views.toString() }, + res: [post1], + }, + { + name: POSTS, + params: { views_gt: post1.views.toString() }, + res: [post2, post3], + }, + { + name: POSTS, + params: { views_gt: (post1.views - 1).toString() }, + res: [post1, post2, post3], + }, + { + name: POSTS, + params: { views_gte: post1.views.toString() }, + res: [post1, post2, post3], + }, + { + data: { posts: [post3, post1, post2] }, + name: POSTS, + params: { _sort: 'views' }, + res: [post1, post2, post3], + }, + { + data: { posts: [post3, post1, post2] }, + name: POSTS, + params: { _sort: '-views' }, + res: [post3, post2, post1], + }, + { + data: { posts: [post3, post1, post2] }, + name: POSTS, + params: { _sort: '-views,id' }, + res: [post3, post2, post1], + }, + + { + name: POSTS, + params: { _start: 0, _end: 2 }, + res: [post1, post2], + }, + { + name: POSTS, + params: { _start: 1, _end: 3 }, + res: [post2, post3], + }, + { + name: POSTS, + params: { _start: 0, _limit: 2 }, + res: [post1, post2], + }, + { + name: POSTS, + params: { _start: 1, _limit: 2 }, + res: [post2, post3], + }, + { + name: POSTS, + params: { _page: 1, _per_page: 2 }, + res: { + first: 1, + last: 2, + prev: null, + next: 2, + pages: 2, + items, + data: [post1, post2], + }, + }, + { + name: POSTS, + params: { _page: 2, _per_page: 2 }, + res: { + first: 1, + last: 2, + prev: 1, + next: null, + pages: 2, + items, + data: [post3], + }, + }, + { + name: POSTS, + params: { _page: 3, _per_page: 2 }, + res: { + first: 1, + last: 2, + prev: 1, + next: null, + pages: 2, + items, + data: [post3], + }, + }, + { + name: POSTS, + params: { _page: 2, _per_page: 1 }, + res: { + first: 1, + last: 3, + prev: 1, + next: 3, + pages: 3, + items, + data: [post2], + }, + }, + { + name: POSTS, + params: { _embed: ['comments'] }, + res: [ + { ...post1, comments: [comment1] }, + { ...post2, comments: [] }, + { ...post3, comments: [] }, + ], + }, + { + name: COMMENTS, + params: { _embed: ['post'] }, + res: [{ ...comment1, post: post1 }], }, - }, - { - name: POSTS, - params: { _page: '2', _per_page: '2' }, - res: { - first: 1, - last: 2, - prev: 1, - next: null, - pages: 2, - items, - data: [post3], + { + name: UNKNOWN_RESOURCE, + res: undefined, }, - }, - { - name: POSTS, - params: { _page: '3', _per_page: '2' }, - res: { - first: 1, - last: 2, - prev: 1, - next: null, - pages: 2, - items, - data: [post3], + { + name: OBJECT, + res: obj, }, - }, - { - name: POSTS, - params: { _embed: ['comments'] }, - res: [ - { ...post1, comments: [comment1] }, - { ...post2, comments: [] }, - { ...post3, comments: [] }, - ], - }, - { - name: COMMENTS, - params: { _embed: ['post'] }, - res: [{ ...comment1, post: post1 }], - }, - { - name: UNKNOWN_RESOURCE, - res: undefined, - }, - { - name: OBJECT, - res: obj, - }, - ] + ] for (const tc of arr) { await t.test(`${tc.name} ${JSON.stringify(tc.params)}`, () => { if (tc.data) { diff --git a/src/service.ts b/src/service.ts index 82ab4d86c..2b108d41a 100644 --- a/src/service.ts +++ b/src/service.ts @@ -171,7 +171,6 @@ export class Service { name: string, query: { [key: string]: unknown - } & { _embed?: string[] _sort?: string _start?: number @@ -307,11 +306,14 @@ export class Service { const start = query._start const end = query._end const limit = query._limit - if (start === undefined && limit) { - return sorted.slice(0, limit) + if (start !== undefined) { + if (end !== undefined) { + return sorted.slice(start, end) + } + return sorted.slice(start, start + (limit || 0)) } - if (start && limit) { - return sorted.slice(start, start + limit) + if (limit !== undefined) { + return sorted.slice(0, limit) } // Paginate