Files
breedr/server/routes/breeding.js

213 lines
6.7 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { getDatabase } = require('../db/init');
// GET all heat cycles for a dog
router.get('/heat-cycles/dog/:dogId', (req, res) => {
try {
const db = getDatabase();
const cycles = db.prepare(`
SELECT * FROM heat_cycles
WHERE dog_id = ?
ORDER BY start_date DESC
`).all(req.params.dogId);
res.json(cycles);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// GET all active heat cycles (with dog info)
router.get('/heat-cycles/active', (req, res) => {
try {
const db = getDatabase();
const cycles = db.prepare(`
SELECT hc.*, d.name as dog_name, d.registration_number, d.breed, d.birth_date
FROM heat_cycles hc
JOIN dogs d ON hc.dog_id = d.id
WHERE hc.end_date IS NULL OR hc.end_date >= date('now', '-30 days')
ORDER BY hc.start_date DESC
`).all();
res.json(cycles);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// GET all heat cycles (all dogs, for calendar population)
router.get('/heat-cycles', (req, res) => {
try {
const db = getDatabase();
const { year, month } = req.query;
let query = `
SELECT hc.*, d.name as dog_name, d.registration_number, d.breed
FROM heat_cycles hc
JOIN dogs d ON hc.dog_id = d.id
`;
const params = [];
if (year && month) {
query += ` WHERE strftime('%Y', hc.start_date) = ? AND strftime('%m', hc.start_date) = ?`;
params.push(year, month.toString().padStart(2, '0'));
}
query += ' ORDER BY hc.start_date DESC';
const cycles = db.prepare(query).all(...params);
res.json(cycles);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// GET breeding date suggestions for a heat cycle
// Returns optimal breeding window based on start_date (days 9-15 of cycle)
router.get('/heat-cycles/:id/suggestions', (req, res) => {
try {
const db = getDatabase();
const cycle = db.prepare(`
SELECT hc.*, d.name as dog_name
FROM heat_cycles hc
JOIN dogs d ON hc.dog_id = d.id
WHERE hc.id = ?
`).get(req.params.id);
if (!cycle) return res.status(404).json({ error: 'Heat cycle not found' });
const start = new Date(cycle.start_date);
const addDays = (d, n) => {
const r = new Date(d);
r.setDate(r.getDate() + n);
return r.toISOString().split('T')[0];
};
// Standard canine heat cycle windows
res.json({
cycle_id: cycle.id,
dog_name: cycle.dog_name,
start_date: cycle.start_date,
windows: [
{
label: 'Proestrus',
description: 'Bleeding begins, not yet receptive',
start: addDays(start, 0),
end: addDays(start, 8),
color: 'pink',
type: 'proestrus'
},
{
label: 'Optimal Breeding Window',
description: 'Estrus — highest fertility, best time to breed',
start: addDays(start, 9),
end: addDays(start, 15),
color: 'green',
type: 'optimal'
},
{
label: 'Late Estrus',
description: 'Fertility declining but breeding still possible',
start: addDays(start, 16),
end: addDays(start, 21),
color: 'yellow',
type: 'late'
},
{
label: 'Diestrus',
description: 'Cycle ending, not receptive',
start: addDays(start, 22),
end: addDays(start, 28),
color: 'gray',
type: 'diestrus'
}
],
// If a breeding_date was logged, compute whelping estimate
whelping: cycle.breeding_date ? {
breeding_date: cycle.breeding_date,
earliest: addDays(new Date(cycle.breeding_date), 58),
expected: addDays(new Date(cycle.breeding_date), 63),
latest: addDays(new Date(cycle.breeding_date), 68)
} : null
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// POST create heat cycle
router.post('/heat-cycles', (req, res) => {
try {
const { dog_id, start_date, end_date, breeding_date, breeding_successful, notes } = req.body;
if (!dog_id || !start_date) {
return res.status(400).json({ error: 'Dog ID and start date are required' });
}
const db = getDatabase();
// Verify dog is female
const dog = db.prepare('SELECT sex FROM dogs WHERE id = ?').get(dog_id);
if (!dog || dog.sex !== 'female') {
return res.status(400).json({ error: 'Dog must be female' });
}
const result = db.prepare(`
INSERT INTO heat_cycles (dog_id, start_date, end_date, breeding_date, breeding_successful, notes)
VALUES (?, ?, ?, ?, ?, ?)
`).run(dog_id, start_date, end_date || null, breeding_date || null, breeding_successful || 0, notes || null);
const cycle = db.prepare('SELECT * FROM heat_cycles WHERE id = ?').get(result.lastInsertRowid);
res.status(201).json(cycle);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// PUT update heat cycle
router.put('/heat-cycles/:id', (req, res) => {
try {
const { start_date, end_date, breeding_date, breeding_successful, notes } = req.body;
const db = getDatabase();
db.prepare(`
UPDATE heat_cycles
SET start_date = ?, end_date = ?, breeding_date = ?, breeding_successful = ?, notes = ?
WHERE id = ?
`).run(start_date, end_date || null, breeding_date || null, breeding_successful || 0, notes || null, req.params.id);
const cycle = db.prepare('SELECT * FROM heat_cycles WHERE id = ?').get(req.params.id);
res.json(cycle);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// DELETE heat cycle
router.delete('/heat-cycles/:id', (req, res) => {
try {
const db = getDatabase();
db.prepare('DELETE FROM heat_cycles WHERE id = ?').run(req.params.id);
res.json({ message: 'Heat cycle deleted successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// GET whelping calculator (standalone)
router.get('/whelping-calculator', (req, res) => {
try {
const { breeding_date } = req.query;
if (!breeding_date) {
return res.status(400).json({ error: 'Breeding date is required' });
}
const breedDate = new Date(breeding_date);
const addDays = (d, n) => { const r = new Date(d); r.setDate(r.getDate() + n); return r.toISOString().split('T')[0]; };
res.json({
breeding_date,
expected_whelping_date: addDays(breedDate, 63),
earliest_date: addDays(breedDate, 58),
latest_date: addDays(breedDate, 68),
gestation_days: 63
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;