const express = require('express'); const router = express.Router(); const ChickenCount = require('../models/ChickenCount'); const Kandang = require('../models/Kandang'); const { validateApiKey } = require('../middleware/auth'); const { isBeforeCutoffTime, getPreviousDate, createResponseMetadata, } = require('../utils/dateUtils'); /** * Normalize a kandang name to a slug: "Kandang Atas" -> "kandang-atas" */ function nameToSlug(name) { return name.toLowerCase().replace(/\s+/g, '-'); } // POST /api/chicken-counting (requires API key) router.post('/', validateApiKey, async (req, res) => { const timestamp = new Date().toISOString(); try { // Check if body is empty or not JSON if (!req.body || Object.keys(req.body).length === 0) { return res.status(400).json({ success: false, error: 'Request body is empty or not valid JSON. Make sure to set Content-Type: application/json', }); } const { date, kandang, filename, total_count } = req.body; // Check missing fields individually for clear error messages const missingFields = []; if (!date) missingFields.push('date'); if (!kandang) missingFields.push('kandang'); if (total_count === undefined || total_count === null) missingFields.push('total_count'); if (missingFields.length > 0) { return res.status(400).json({ success: false, error: `Missing required fields: ${missingFields.join(', ')}`, details: { missing: missingFields, received: { date: date || null, kandang: kandang || null, total_count: total_count !== undefined ? total_count : null, filename: filename || null, }, }, }); } // Validate date format if (typeof date !== 'string') { return res.status(400).json({ success: false, error: `Invalid date type. Expected string, got ${typeof date}`, details: { received: date, expected: 'YYYY-MM-DD' }, }); } if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) { return res.status(400).json({ success: false, error: `Invalid date format: "${date}". Use YYYY-MM-DD (e.g. "2026-03-01")`, details: { received: date, expected: 'YYYY-MM-DD' }, }); } // Validate date is a real date const parsedDate = new Date(date + 'T00:00:00Z'); if (isNaN(parsedDate.getTime())) { return res.status(400).json({ success: false, error: `Invalid date value: "${date}". The date does not exist.`, details: { received: date }, }); } // Validate kandang type if (typeof kandang !== 'string') { return res.status(400).json({ success: false, error: `Invalid kandang type. Expected string, got ${typeof kandang}`, details: { received: kandang, expected: 'string (e.g. "kandang-atas")' }, }); } // Validate total_count type and value if (typeof total_count !== 'number') { return res.status(400).json({ success: false, error: `Invalid total_count type. Expected number, got ${typeof total_count}`, details: { received: total_count, expected: 'number (e.g. 5612)' }, }); } if (!Number.isInteger(total_count)) { return res.status(400).json({ success: false, error: `total_count must be an integer, got ${total_count}`, details: { received: total_count }, }); } if (total_count < 0) { return res.status(400).json({ success: false, error: `total_count cannot be negative`, details: { received: total_count }, }); } // Validate filename type if provided if (filename !== undefined && filename !== null && typeof filename !== 'string') { return res.status(400).json({ success: false, error: `Invalid filename type. Expected string, got ${typeof filename}`, details: { received: filename }, }); } let allKandangs; try { allKandangs = await Kandang.getAll(); } catch (dbError) { console.error(`[${timestamp}] ERROR: Failed to query kandangs table:`, dbError.message); return res.status(500).json({ success: false, error: 'Database error: Failed to query kandangs table', details: { message: dbError.message }, }); } if (allKandangs.length === 0) { return res.status(500).json({ success: false, error: 'No kandangs configured in the system. Please create kandangs first via /api/kandangs', }); } const matched = allKandangs.find((k) => nameToSlug(k.name) === kandang); const validSlugs = allKandangs.map((k) => ({ slug: nameToSlug(k.name), name: k.name, id: k.id, })); if (!matched) { return res.status(400).json({ success: false, error: `Kandang '${kandang}' not found`, details: { received: kandang, available: validSlugs, }, }); } // Check if this is a manual update from UI (source=manual query param) const isManualUpdate = req.query.source === 'manual'; const operation = isManualUpdate ? 'Upserting (manual)' : 'Inserting (API, skip if exists)'; // Use upsert for manual updates, insertIfNotExists for API calls let record; try { if (isManualUpdate) { record = await ChickenCount.upsert(date, matched.id, filename || null, total_count); } else { record = await ChickenCount.insertIfNotExists( date, matched.id, filename || null, total_count ); } } catch (dbError) { console.error(`[${timestamp}] ERROR: Failed to save chicken count:`, dbError.message); console.error(`[${timestamp}] Stack:`, dbError.stack); return res.status(500).json({ success: false, error: 'Database error: Failed to save chicken count', details: { message: dbError.message }, }); } if (record.skipped) { } else { } res.status(200).json({ success: true, data: record, }); } catch (error) { console.error(`[${timestamp}] UNEXPECTED ERROR:`, error.message); console.error(`[${timestamp}] Stack:`, error.stack); res.status(500).json({ success: false, error: 'Internal server error', details: { message: error.message }, }); } }); // GET /api/chicken-counting?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD&kandangId=1 router.get('/', async (req, res) => { try { let { startDate, endDate, kandangId } = req.query; if (!startDate || !endDate) { return res.status(400).json({ success: false, error: 'startDate and endDate query parameters are required (YYYY-MM-DD)', }); } // Apply D-1 filter if querying single day and before cutoff (17:00 WIB) let isD1Applied = false; if (startDate === endDate && isBeforeCutoffTime()) { startDate = getPreviousDate(startDate); endDate = startDate; isD1Applied = true; } const records = await ChickenCount.getByDateRange( startDate, endDate, kandangId ? parseInt(kandangId) : null ); res.json({ success: true, data: records, meta: createResponseMetadata(isD1Applied), }); } catch (error) { console.error('Error querying chicken counting:', error); res.status(500).json({ success: false, error: error.message, }); } }); module.exports = router;