fix: add pagination to unbounded GET endpoints
All list endpoints now accept ?page and ?limit (default 50, max 200) and
return { data, total, page, limit } instead of a bare array, preventing
memory and performance failures at scale.
- GET /api/dogs: adds pagination, server-side search (?search) and sex
filter (?sex), and a stats aggregate (total/males/females) for the
Dashboard to avoid counting from the array
- GET /api/litters: adds pagination; also fixes N+1 query by fetching
all puppies for the current page in a single query instead of one per
litter
- DogList: moves search/sex filtering server-side with 300ms debounce;
adds Prev/Next pagination controls
- LitterList: uses paginated response; adds Prev/Next pagination controls
- Dashboard: reads counts from stats/total fields instead of array length
- LitterDetail, LitterForm: switch dogs fetch to /api/dogs/all (complete
list, no pagination, for sire/dam dropdowns)
- DogForm: updates litters fetch to use paginated response shape
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,34 +55,72 @@ function attachParents(db, dogs) {
|
||||
return dogs;
|
||||
}
|
||||
|
||||
// ── GET dogs
|
||||
// ── GET dogs (paginated)
|
||||
// Default: kennel dogs only (is_external = 0)
|
||||
// ?include_external=1 : all active dogs (kennel + external)
|
||||
// ?external_only=1 : external dogs only
|
||||
// ?page=1&limit=50 : pagination
|
||||
// ?search=term : filter by name or registration_number
|
||||
// ?sex=male|female : filter by sex
|
||||
// Response: { data, total, page, limit, stats: { total, males, females } }
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const includeExternal = req.query.include_external === '1' || req.query.include_external === 'true';
|
||||
const externalOnly = req.query.external_only === '1' || req.query.external_only === 'true';
|
||||
const search = (req.query.search || '').trim();
|
||||
const sex = req.query.sex === 'male' || req.query.sex === 'female' ? req.query.sex : '';
|
||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
||||
const limit = Math.min(200, Math.max(1, parseInt(req.query.limit, 10) || 50));
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause;
|
||||
let baseWhere;
|
||||
if (externalOnly) {
|
||||
whereClause = 'WHERE is_active = 1 AND is_external = 1';
|
||||
baseWhere = 'is_active = 1 AND is_external = 1';
|
||||
} else if (includeExternal) {
|
||||
whereClause = 'WHERE is_active = 1';
|
||||
baseWhere = 'is_active = 1';
|
||||
} else {
|
||||
whereClause = 'WHERE is_active = 1 AND is_external = 0';
|
||||
baseWhere = 'is_active = 1 AND is_external = 0';
|
||||
}
|
||||
|
||||
const filters = [];
|
||||
const params = [];
|
||||
if (search) {
|
||||
filters.push('(name LIKE ? OR registration_number LIKE ?)');
|
||||
params.push(`%${search}%`, `%${search}%`);
|
||||
}
|
||||
if (sex) {
|
||||
filters.push('sex = ?');
|
||||
params.push(sex);
|
||||
}
|
||||
|
||||
const whereClause = 'WHERE ' + [baseWhere, ...filters].join(' AND ');
|
||||
|
||||
const total = db.prepare(`SELECT COUNT(*) as count FROM dogs ${whereClause}`).get(...params).count;
|
||||
|
||||
const statsWhere = externalOnly
|
||||
? 'WHERE is_active = 1 AND is_external = 1'
|
||||
: includeExternal
|
||||
? 'WHERE is_active = 1'
|
||||
: 'WHERE is_active = 1 AND is_external = 0';
|
||||
const stats = db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN sex = 'male' THEN 1 ELSE 0 END) as males,
|
||||
SUM(CASE WHEN sex = 'female' THEN 1 ELSE 0 END) as females
|
||||
FROM dogs ${statsWhere}
|
||||
`).get();
|
||||
|
||||
const dogs = db.prepare(`
|
||||
SELECT ${DOG_COLS}
|
||||
FROM dogs
|
||||
${whereClause}
|
||||
ORDER BY name
|
||||
`).all();
|
||||
LIMIT ? OFFSET ?
|
||||
`).all(...params, limit, offset);
|
||||
|
||||
res.json(attachParents(db, dogs));
|
||||
res.json({ data: attachParents(db, dogs), total, page, limit, stats });
|
||||
} catch (error) {
|
||||
console.error('Error fetching dogs:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
|
||||
Reference in New Issue
Block a user