From 7485c8174444d8a1a61f2aa3a2600452705ed864 Mon Sep 17 00:00:00 2001 From: Tomasz Seredynski <123777101+MechanikGamer@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:57:00 +0100 Subject: [PATCH] Add pagination support to tips API Add reusable pagination helpers and wire paginated responses into the tips service and controller. Created src/utils/pagination.js (parsePagination, paginateArray), updated src/services/tipService.js to optionally return {data, pagination} for getAll and getByTopic while preserving backward-compatible behavior, and updated src/controllers/tipController.js to parse query params and return paginated results. Also added a .prettierrc file and made minor code/style tweaks and logging improvements. --- .prettierrc | 11 +++++++++ src/controllers/tipController.js | 16 +++++++++---- src/services/tipService.js | 39 ++++++++++++++++++++++---------- src/utils/pagination.js | 39 ++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 .prettierrc create mode 100644 src/utils/pagination.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e0dc1c1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "printWidth": 120, + "semi": true, + "trailingComma": "none", + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf" +} \ No newline at end of file diff --git a/src/controllers/tipController.js b/src/controllers/tipController.js index 125d4ce..f38bb31 100644 --- a/src/controllers/tipController.js +++ b/src/controllers/tipController.js @@ -3,16 +3,22 @@ * Includes emoji logging for fun debugging experience */ import * as tipService from '../services/tipService.js'; +import { parsePagination } from '../utils/pagination.js'; /** - * Get all tips + * Get all tips (paginated) */ export const getAll = async (req, res, next) => { try { - console.log('📝 Fetching all tips...'); - const tips = await tipService.getAll(); - console.log(`✅ Found ${tips.length} tips`); - res.json(tips); + const { page, limit } = parsePagination(req.query, { page: 1, limit: 10 }); + + console.log(`📝 Fetching tips... page=${page}, limit=${limit}`); + + const result = await tipService.getAll({ page, limit }); + + console.log(`✅ Returned ${result.data.length} tips (total ${result.pagination.totalCount})`); + + res.json(result); } catch (error) { console.log('❌ Error fetching tips:', error.message); next(error); diff --git a/src/services/tipService.js b/src/services/tipService.js index 7878c7c..f083a56 100644 --- a/src/services/tipService.js +++ b/src/services/tipService.js @@ -5,6 +5,7 @@ import { readFile, writeFile } from 'fs/promises'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; +import { paginateArray } from '../utils/pagination.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -18,7 +19,7 @@ async function readTips() { const data = await readFile(DATA_FILE, 'utf8'); const parsed = JSON.parse(data); // Handle both array format and {tips: []} format - return Array.isArray(parsed) ? parsed : (parsed.tips || []); + return Array.isArray(parsed) ? parsed : parsed.tips || []; } catch (error) { console.log('⚠️ Could not read tips file, returning empty array'); return []; @@ -33,10 +34,18 @@ async function writeTips(tips) { } /** - * Get all tips + * Get all tips (optionally paginated) + * + * If pagination is provided -> returns { data, pagination } + * Else (legacy) -> returns full array */ -export const getAll = async () => { - return readTips(); +export const getAll = async (options = undefined) => { + const tips = await readTips(); + + if (!options) return tips; + + const { page = 1, limit = 10 } = options; + return paginateArray(tips, { page, limit }); }; /** @@ -52,26 +61,32 @@ export const getRandom = async () => { /** * Get tip by ID */ -export const getById = async (id) => { +export const getById = async id => { const tips = await readTips(); // Support both string and number IDs return tips.find(tip => String(tip.id) === String(id)); }; /** - * Get tips by topic + * Get tips by topic (optionally paginated) + * + * If options is provided -> returns { data, pagination } + * Else -> returns array */ -export const getByTopic = async (topic) => { +export const getByTopic = async (topic, options = undefined) => { const tips = await readTips(); - return tips.filter(tip => - tip.topic?.toLowerCase() === topic.toLowerCase() - ); + const filtered = tips.filter(tip => tip.topic?.toLowerCase() === topic.toLowerCase()); + + if (!options) return filtered; + + const { page = 1, limit = 10 } = options; + return paginateArray(filtered, { page, limit }); }; /** * Create a new tip */ -export const create = async (data) => { +export const create = async data => { const tips = await readTips(); const newTip = { id: String(tips.length + 1), @@ -105,7 +120,7 @@ export const update = async (id, data) => { /** * Delete a tip */ -export const remove = async (id) => { +export const remove = async id => { const tips = await readTips(); const index = tips.findIndex(tip => String(tip.id) === String(id)); if (index === -1) return false; diff --git a/src/utils/pagination.js b/src/utils/pagination.js new file mode 100644 index 0000000..9f9c696 --- /dev/null +++ b/src/utils/pagination.js @@ -0,0 +1,39 @@ +/** + * Pagination helpers (reusable for other endpoints) + */ + +export function parsePagination(query = {}, defaults = { page: 1, limit: 10 }) { + const pageRaw = query.page; + const limitRaw = query.limit; + + const page = toPositiveInt(pageRaw, defaults.page); + const limit = toPositiveInt(limitRaw, defaults.limit); + + return { page, limit }; +} + +export function paginateArray(items, { page = 1, limit = 10 } = {}) { + const totalCount = items.length; + + const totalPages = Math.max(1, Math.ceil(totalCount / limit)); + + const start = (page - 1) * limit; + const end = start + limit; + + const data = items.slice(start, end); + + return { + data, + pagination: { + currentPage: page, + totalPages, + totalCount, + hasNextPage: page < totalPages, + }, + }; +} + +function toPositiveInt(value, fallback) { + const n = Number.parseInt(value, 10); + return Number.isFinite(n) && n > 0 ? n : fallback; +}