const express = require('express'); const db = require('../db/models'); const { wrapAsync } = require('../helpers'); const router = express.Router(); const Sequelize = db.Sequelize; const Op = Sequelize.Op; const searchableFields = [ 'voter_number', 'national_id_number', 'name_bn', 'name_en', 'father_name', 'mother_name', 'spouse_name', 'address_line', 'district', 'upazila', 'union_ward', 'village_mohalla', 'polling_center', 'raw_text_snippet', ]; function cleanText(value, maxLength = 120) { if (!value || typeof value !== 'string') return ''; return value.trim().slice(0, maxLength); } function buildSearchWhere(query) { const q = cleanText(query.q); const mode = ['all', 'name', 'voter'].includes(query.mode) ? query.mode : 'all'; const where = {}; if (q) { if (mode === 'name') { where[Op.or] = [ { name_bn: { [Op.iLike]: `%${q}%` } }, { name_en: { [Op.iLike]: `%${q}%` } }, { father_name: { [Op.iLike]: `%${q}%` } }, { mother_name: { [Op.iLike]: `%${q}%` } }, { spouse_name: { [Op.iLike]: `%${q}%` } }, ]; } else if (mode === 'voter') { where[Op.or] = [ { voter_number: { [Op.iLike]: `%${q}%` } }, { national_id_number: { [Op.iLike]: `%${q}%` } }, ]; } else { where[Op.or] = searchableFields.map((field) => ({ [field]: { [Op.iLike]: `%${q}%` }, })); } } const district = cleanText(query.district, 80); const upazila = cleanText(query.upazila, 80); const gender = cleanText(query.gender, 20); if (district) where.district = { [Op.iLike]: `%${district}%` }; if (upazila) where.upazila = { [Op.iLike]: `%${upazila}%` }; if (['male', 'female', 'other', 'unknown'].includes(gender)) where.gender = gender; return where; } router.get('/summary', wrapAsync(async (req, res) => { const [totalRecords, totalPdfs, maleRecords, femaleRecords, areaRows, latestPdfs] = await Promise.all([ db.voter_records.count(), db.pdf_documents.count(), db.voter_records.count({ where: { gender: 'male' } }), db.voter_records.count({ where: { gender: 'female' } }), db.voter_records.findAll({ attributes: [ [Sequelize.fn('COALESCE', Sequelize.col('district'), 'অনির্ধারিত'), 'area'], [Sequelize.fn('COUNT', Sequelize.col('voter_records.id')), 'count'], ], group: [Sequelize.fn('COALESCE', Sequelize.col('district'), 'অনির্ধারিত')], order: [[Sequelize.literal('count'), 'DESC']], limit: 5, raw: true, }), db.pdf_documents.findAll({ attributes: ['id', 'document_name', 'processing_status', 'uploaded_at', 'createdAt'], order: [['createdAt', 'DESC']], limit: 6, raw: true, }), ]); res.status(200).send({ totals: { voters: totalRecords, pdfs: totalPdfs, male: maleRecords, female: femaleRecords, }, areas: areaRows.map((row) => ({ area: row.area, count: Number(row.count) })), latestPdfs, }); })); router.get('/search', wrapAsync(async (req, res) => { const limit = Math.min(Number(req.query.limit) || 25, 100); const page = Math.max(Number(req.query.page) || 0, 0); const where = buildSearchWhere(req.query); const payload = await db.voter_records.findAndCountAll({ where, include: [ { model: db.pdf_documents, as: 'source_document', attributes: ['id', 'document_name', 'processing_status'], required: false, }, ], distinct: true, order: [['createdAt', 'DESC']], limit, offset: page * limit, }); res.status(200).send({ rows: payload.rows, count: payload.count }); })); router.use('/', require('../helpers').commonErrorHandler); module.exports = router;