131 lines
3.7 KiB
JavaScript
131 lines
3.7 KiB
JavaScript
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;
|