Add self-healing for pincode taluk field
This commit is contained in:
parent
dc49d6f432
commit
5c964d1917
1
data/IFSC.csv
Normal file
1
data/IFSC.csv
Normal file
@ -0,0 +1 @@
|
|||||||
|
404: Not Found
|
||||||
|
1446
data/IFSC_complete.csv
Normal file
1446
data/IFSC_complete.csv
Normal file
File diff suppressed because one or more lines are too long
1
data/rbi_ifsc.json
Normal file
1
data/rbi_ifsc.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
404: Not Found
|
||||||
113
src/database/importIFSC.js
Normal file
113
src/database/importIFSC.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Import comprehensive IFSC data from Razorpay API
|
||||||
|
* This script fetches IFSC codes for major banks and imports them to the database
|
||||||
|
*/
|
||||||
|
|
||||||
|
const axios = require('axios');
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:fXlNbqBpxhgchmrvPtlMKgwttWVdMDlO@shuttle.proxy.rlwy.net:23879/railway'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Major Indian banks with their IFSC prefixes
|
||||||
|
const BANK_PREFIXES = [
|
||||||
|
{ prefix: 'SBIN', name: 'State Bank of India', count: 100 },
|
||||||
|
{ prefix: 'HDFC', name: 'HDFC Bank', count: 100 },
|
||||||
|
{ prefix: 'ICIC', name: 'ICICI Bank', count: 100 },
|
||||||
|
{ prefix: 'KKBK', name: 'Kotak Mahindra Bank', count: 50 },
|
||||||
|
{ prefix: 'UTIB', name: 'Axis Bank', count: 50 },
|
||||||
|
{ prefix: 'PUNB', name: 'Punjab National Bank', count: 50 },
|
||||||
|
{ prefix: 'BARB', name: 'Bank of Baroda', count: 50 },
|
||||||
|
{ prefix: 'CNRB', name: 'Canara Bank', count: 50 },
|
||||||
|
{ prefix: 'UBIN', name: 'Union Bank of India', count: 50 },
|
||||||
|
{ prefix: 'IOBA', name: 'Indian Overseas Bank', count: 30 },
|
||||||
|
{ prefix: 'IDIB', name: 'Indian Bank', count: 30 },
|
||||||
|
{ prefix: 'BKID', name: 'Bank of India', count: 30 },
|
||||||
|
{ prefix: 'CBIN', name: 'Central Bank of India', count: 30 },
|
||||||
|
{ prefix: 'YESB', name: 'Yes Bank', count: 30 },
|
||||||
|
{ prefix: 'INDB', name: 'IndusInd Bank', count: 30 },
|
||||||
|
{ prefix: 'FDRL', name: 'Federal Bank', count: 30 },
|
||||||
|
{ prefix: 'KVBL', name: 'Karur Vysya Bank', count: 20 },
|
||||||
|
{ prefix: 'TMBL', name: 'Tamilnad Mercantile Bank', count: 20 },
|
||||||
|
{ prefix: 'RATN', name: 'RBL Bank', count: 20 },
|
||||||
|
{ prefix: 'IDFB', name: 'IDFC First Bank', count: 20 },
|
||||||
|
];
|
||||||
|
|
||||||
|
async function fetchAndInsertIFSC(prefix, bankName, count) {
|
||||||
|
const inserted = [];
|
||||||
|
const failed = [];
|
||||||
|
|
||||||
|
// Generate IFSC codes to try
|
||||||
|
for (let i = 0; i <= count; i++) {
|
||||||
|
const ifsc = `${prefix}${String(i).padStart(7, '0')}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://ifsc.razorpay.com/${ifsc}`, { timeout: 5000 });
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
// Insert into database
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO ifsc_codes (ifsc, bank_name, branch, address, city, district, state, contact, upi_enabled, rtgs_enabled, neft_enabled, imps_enabled, micr_code, swift_code, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW())
|
||||||
|
ON CONFLICT (ifsc) DO UPDATE SET
|
||||||
|
bank_name = EXCLUDED.bank_name,
|
||||||
|
branch = EXCLUDED.branch,
|
||||||
|
address = EXCLUDED.address,
|
||||||
|
city = EXCLUDED.city,
|
||||||
|
updated_at = NOW()`,
|
||||||
|
[
|
||||||
|
ifsc,
|
||||||
|
bankName,
|
||||||
|
data.BRANCH || '',
|
||||||
|
data.ADDRESS || '',
|
||||||
|
data.CITY || '',
|
||||||
|
data.DISTRICT || '',
|
||||||
|
data.STATE || '',
|
||||||
|
data.CONTACT || '',
|
||||||
|
data.UPI || false,
|
||||||
|
data.RTGS || false,
|
||||||
|
data.NEFT || false,
|
||||||
|
data.IMPS || false,
|
||||||
|
data.MICR || '',
|
||||||
|
data.SWIFT || ''
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
inserted.push(ifsc);
|
||||||
|
process.stdout.write(`✅ ${ifsc} `);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.status === 404) {
|
||||||
|
// IFSC doesn't exist, skip
|
||||||
|
} else {
|
||||||
|
failed.push(ifsc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to not overwhelm the API
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { inserted: inserted.length, failed: failed.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🚀 Starting comprehensive IFSC import...\n');
|
||||||
|
|
||||||
|
let totalInserted = 0;
|
||||||
|
|
||||||
|
for (const bank of BANK_PREFIXES) {
|
||||||
|
console.log(`\n📦 Fetching ${bank.name} (${bank.prefix})...`);
|
||||||
|
const result = await fetchAndInsertIFSC(bank.prefix, bank.name, bank.count);
|
||||||
|
totalInserted += result.inserted;
|
||||||
|
console.log(`\n ✅ Inserted: ${result.inserted} codes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n==================================================`);
|
||||||
|
console.log(`🎉 Total IFSC codes imported: ${totalInserted}`);
|
||||||
|
console.log(`==================================================\n`);
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
92
src/database/importPincodes.js
Normal file
92
src/database/importPincodes.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* Import missing metro city pincodes from India Post API
|
||||||
|
*/
|
||||||
|
|
||||||
|
const axios = require('axios');
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:fXlNbqBpxhgchmrvPtlMKgwttWVdMDlO@shuttle.proxy.rlwy.net:23879/railway'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Major metro pincodes that need to be added
|
||||||
|
const METRO_PINCODES = [
|
||||||
|
// Mumbai
|
||||||
|
'400001', '400002', '400003', '400004', '400005', '400006', '400007', '400008', '400009', '400010',
|
||||||
|
'400011', '400012', '400013', '400015', '400016', '400017', '400018', '400019', '400020', '400021',
|
||||||
|
'400022', '400023', '400024', '400025', '400026', '400027', '400028', '400029', '400030', '400031',
|
||||||
|
'400032', '400033', '400034', '400036', '400037', '400038', '400039', '400042', '400043', '400049',
|
||||||
|
'400050', '400051', '400052', '400053', '400054', '400055', '400056', '400057', '400058', '400059',
|
||||||
|
'400060', '400061', '400062', '400063', '400064', '400065', '400066', '400067', '400068', '400069',
|
||||||
|
'400070', '400071', '400072', '400074', '400075', '400076', '400077', '400078', '400079', '400080',
|
||||||
|
'400081', '400082', '400083', '400084', '400085', '400086', '400087', '400088', '400089', '400091',
|
||||||
|
'400092', '400093', '400094', '400095', '400096', '400097', '400098', '400099', '400101', '400102',
|
||||||
|
// Chennai
|
||||||
|
'600001', '600002', '600003', '600004', '600005', '600006', '600007', '600008', '600009', '600010',
|
||||||
|
'600011', '600012', '600013', '600014', '600015', '600016', '600017', '600018', '600019', '600020',
|
||||||
|
'600021', '600022', '600023', '600024', '600025', '600026', '600027', '600028', '600029', '600030',
|
||||||
|
'600031', '600032', '600033', '600034', '600035', '600036', '600037', '600038', '600039', '600040',
|
||||||
|
// Pune
|
||||||
|
'411001', '411002', '411003', '411004', '411005', '411006', '411007', '411008', '411009', '411010',
|
||||||
|
'411011', '411012', '411013', '411014', '411015', '411016', '411017', '411018', '411019', '411020',
|
||||||
|
];
|
||||||
|
|
||||||
|
async function fetchAndInsertPincode(pincode) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://api.postalpincode.in/pincode/${pincode}`, { timeout: 10000 });
|
||||||
|
|
||||||
|
if (response.data && response.data[0] && response.data[0].Status === 'Success') {
|
||||||
|
const postOffices = response.data[0].PostOffice;
|
||||||
|
|
||||||
|
for (const po of postOffices) {
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO pincodes (pincode, office_name, office_type, district, division, region, state, latitude, longitude, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
|
||||||
|
ON CONFLICT (pincode, office_name) DO UPDATE SET
|
||||||
|
district = EXCLUDED.district,
|
||||||
|
state = EXCLUDED.state,
|
||||||
|
updated_at = NOW()`,
|
||||||
|
[
|
||||||
|
pincode,
|
||||||
|
po.Name || '',
|
||||||
|
po.BranchType || 'PO',
|
||||||
|
po.District || '',
|
||||||
|
po.Division || '',
|
||||||
|
po.Region || '',
|
||||||
|
po.State || '',
|
||||||
|
'0',
|
||||||
|
'0'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return postOffices.length;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (error) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🚀 Starting metro pincode import...\n');
|
||||||
|
|
||||||
|
let totalInserted = 0;
|
||||||
|
|
||||||
|
for (const pincode of METRO_PINCODES) {
|
||||||
|
const count = await fetchAndInsertPincode(pincode);
|
||||||
|
if (count > 0) {
|
||||||
|
process.stdout.write(`✅ ${pincode} (${count}) `);
|
||||||
|
totalInserted += count;
|
||||||
|
}
|
||||||
|
// Delay to respect API rate limits
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n\n==================================================`);
|
||||||
|
console.log(`🎉 Total post offices imported: ${totalInserted}`);
|
||||||
|
console.log(`==================================================\n`);
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
@ -118,6 +118,7 @@ CREATE TABLE IF NOT EXISTS pincodes (
|
|||||||
division VARCHAR(100),
|
division VARCHAR(100),
|
||||||
region VARCHAR(100),
|
region VARCHAR(100),
|
||||||
state VARCHAR(100),
|
state VARCHAR(100),
|
||||||
|
block VARCHAR(100),
|
||||||
latitude DECIMAL(10, 8),
|
latitude DECIMAL(10, 8),
|
||||||
longitude DECIMAL(11, 8),
|
longitude DECIMAL(11, 8),
|
||||||
updated_at TIMESTAMP DEFAULT NOW()
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
|||||||
@ -25,7 +25,7 @@ function generateApiKey() {
|
|||||||
*/
|
*/
|
||||||
router.post('/signup', async (req, res, next) => {
|
router.post('/signup', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { email, password, company_name, phone } = req.body;
|
const { email, password, name, phone } = req.body;
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
throw new ApiError(400, 'MISSING_FIELDS', 'Email and password required');
|
throw new ApiError(400, 'MISSING_FIELDS', 'Email and password required');
|
||||||
@ -49,7 +49,7 @@ router.post('/signup', async (req, res, next) => {
|
|||||||
`INSERT INTO users (email, password_hash, company_name, phone, plan, monthly_quota, quota_reset_date)
|
`INSERT INTO users (email, password_hash, company_name, phone, plan, monthly_quota, quota_reset_date)
|
||||||
VALUES ($1, $2, $3, $4, 'free', 100, DATE(NOW() + INTERVAL '1 month'))
|
VALUES ($1, $2, $3, $4, 'free', 100, DATE(NOW() + INTERVAL '1 month'))
|
||||||
RETURNING id, email, company_name, plan`,
|
RETURNING id, email, company_name, plan`,
|
||||||
[email.toLowerCase(), passwordHash, company_name, phone]
|
[email.toLowerCase(), passwordHash, name, phone]
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = result.rows[0];
|
const user = result.rows[0];
|
||||||
@ -79,7 +79,7 @@ router.post('/signup', async (req, res, next) => {
|
|||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
company_name: user.company_name,
|
name: user.company_name,
|
||||||
plan: user.plan
|
plan: user.plan
|
||||||
},
|
},
|
||||||
api_key: apiKey,
|
api_key: apiKey,
|
||||||
@ -139,7 +139,7 @@ router.post('/login', async (req, res, next) => {
|
|||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
company_name: user.company_name,
|
name: user.company_name,
|
||||||
plan: user.plan,
|
plan: user.plan,
|
||||||
quota: user.monthly_quota,
|
quota: user.monthly_quota,
|
||||||
used: user.calls_this_month
|
used: user.calls_this_month
|
||||||
|
|||||||
@ -1,16 +1,94 @@
|
|||||||
/**
|
/**
|
||||||
* IFSC Lookup Route
|
* IFSC Lookup Route
|
||||||
* Returns bank details for a given IFSC code
|
* Returns bank details for a given IFSC code
|
||||||
|
* Supports local database + Razorpay API fallback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const axios = require('axios');
|
||||||
const { authenticateApiKey } = require('../middleware/auth');
|
const { authenticateApiKey } = require('../middleware/auth');
|
||||||
const { rateLimit } = require('../middleware/rateLimit');
|
const { rateLimit } = require('../middleware/rateLimit');
|
||||||
const { query } = require('../database/connection');
|
const { query } = require('../database/connection');
|
||||||
const { cacheGet, cacheSet } = require('../cache/redis');
|
const { cacheGet, cacheSet } = require('../cache/redis');
|
||||||
const { logApiCall } = require('../services/analytics');
|
const { logApiCall } = require('../services/analytics');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch IFSC from Razorpay API and save to database
|
||||||
|
*/
|
||||||
|
async function fetchFromRazorpayAPI(ifsc) {
|
||||||
|
try {
|
||||||
|
console.log(`🌐 Fetching IFSC ${ifsc} from Razorpay API...`);
|
||||||
|
|
||||||
|
const response = await axios.get(`https://ifsc.razorpay.com/${ifsc}`, {
|
||||||
|
timeout: 10000
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.BANK) {
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Save to database for future lookups
|
||||||
|
await query(
|
||||||
|
`INSERT INTO ifsc_codes (ifsc, bank_name, branch, address, city, district, state, contact, upi_enabled, rtgs_enabled, neft_enabled, imps_enabled, micr_code, swift_code, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW())`,
|
||||||
|
[
|
||||||
|
ifsc,
|
||||||
|
data.BANK || '',
|
||||||
|
data.BRANCH || '',
|
||||||
|
data.ADDRESS || '',
|
||||||
|
data.CITY || '',
|
||||||
|
data.DISTRICT || '',
|
||||||
|
data.STATE || '',
|
||||||
|
data.CONTACT || '',
|
||||||
|
data.UPI || false,
|
||||||
|
data.RTGS || false,
|
||||||
|
data.NEFT || false,
|
||||||
|
data.IMPS || false,
|
||||||
|
data.MICR || '',
|
||||||
|
data.SWIFT || null
|
||||||
|
]
|
||||||
|
);
|
||||||
|
console.log(`✅ Saved IFSC ${ifsc} to database`);
|
||||||
|
} catch (insertError) {
|
||||||
|
// Ignore duplicate errors
|
||||||
|
console.log(`⚠️ Could not save to DB (might already exist):`, insertError.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return normalized data
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
ifsc: ifsc,
|
||||||
|
bank_name: data.BANK || '',
|
||||||
|
branch: data.BRANCH || '',
|
||||||
|
address: data.ADDRESS || '',
|
||||||
|
city: data.CITY || '',
|
||||||
|
district: data.DISTRICT || '',
|
||||||
|
state: data.STATE || '',
|
||||||
|
contact: data.CONTACT || '',
|
||||||
|
upi_enabled: data.UPI || false,
|
||||||
|
rtgs_enabled: data.RTGS || false,
|
||||||
|
neft_enabled: data.NEFT || false,
|
||||||
|
imps_enabled: data.IMPS || false,
|
||||||
|
micr_code: data.MICR || '',
|
||||||
|
swift_code: data.SWIFT || null
|
||||||
|
},
|
||||||
|
source: 'RAZORPAY_API'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, error: 'Invalid IFSC code' };
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.status === 404) {
|
||||||
|
console.log(`❌ IFSC ${ifsc} not found in Razorpay API`);
|
||||||
|
return { success: false, error: 'IFSC code not found' };
|
||||||
|
}
|
||||||
|
console.error(`❌ Razorpay API error for ${ifsc}:`, error.message);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
router.use(authenticateApiKey);
|
router.use(authenticateApiKey);
|
||||||
router.use(rateLimit);
|
router.use(rateLimit);
|
||||||
|
|
||||||
@ -133,6 +211,7 @@ router.get('/state/:stateName', async (req, res, next) => {
|
|||||||
router.get('/:ifsc_code', async (req, res, next) => {
|
router.get('/:ifsc_code', async (req, res, next) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let success = false;
|
let success = false;
|
||||||
|
let source = 'LOCAL_DB';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { ifsc_code } = req.params;
|
const { ifsc_code } = req.params;
|
||||||
@ -149,17 +228,30 @@ router.get('/:ifsc_code', async (req, res, next) => {
|
|||||||
const cacheKey = `ifsc:${ifsc}`;
|
const cacheKey = `ifsc:${ifsc}`;
|
||||||
let data = await cacheGet(cacheKey);
|
let data = await cacheGet(cacheKey);
|
||||||
|
|
||||||
if (!data) {
|
if (data) {
|
||||||
|
source = 'CACHE';
|
||||||
|
} else {
|
||||||
|
// Try local database first
|
||||||
const result = await query('SELECT * FROM ifsc_codes WHERE ifsc = $1', [ifsc]);
|
const result = await query('SELECT * FROM ifsc_codes WHERE ifsc = $1', [ifsc]);
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
if (result.rows.length === 0) {
|
||||||
return res.status(404).json({
|
// Fallback to Razorpay API
|
||||||
success: false,
|
console.log(`📦 IFSC ${ifsc} not in DB, trying Razorpay API...`);
|
||||||
error: { code: 'IFSC_NOT_FOUND', message: 'IFSC not found' }
|
const apiResult = await fetchFromRazorpayAPI(ifsc);
|
||||||
});
|
|
||||||
|
if (!apiResult.success) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: { code: 'IFSC_NOT_FOUND', message: 'IFSC not found in any source' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
data = apiResult.data;
|
||||||
|
source = 'RAZORPAY_API';
|
||||||
|
} else {
|
||||||
|
data = result.rows[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
data = result.rows[0];
|
|
||||||
await cacheSet(cacheKey, data, 86400); // Cache for 24 hours
|
await cacheSet(cacheKey, data, 86400); // Cache for 24 hours
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +261,7 @@ router.get('/:ifsc_code', async (req, res, next) => {
|
|||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
ifsc: data.ifsc,
|
ifsc: data.ifsc,
|
||||||
|
source,
|
||||||
bank: data.bank_name,
|
bank: data.bank_name,
|
||||||
branch: data.branch,
|
branch: data.branch,
|
||||||
address: data.address,
|
address: data.address,
|
||||||
|
|||||||
@ -1,16 +1,112 @@
|
|||||||
/**
|
/**
|
||||||
* Pincode Lookup Route
|
* Pincode Lookup Route
|
||||||
* Returns location details for a given pincode
|
* Returns location details for a given pincode
|
||||||
|
* Supports local database + India Post API fallback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const axios = require('axios');
|
||||||
const { authenticateApiKey } = require('../middleware/auth');
|
const { authenticateApiKey } = require('../middleware/auth');
|
||||||
const { rateLimit } = require('../middleware/rateLimit');
|
const { rateLimit } = require('../middleware/rateLimit');
|
||||||
const { query } = require('../database/connection');
|
const { query } = require('../database/connection');
|
||||||
const { cacheGet, cacheSet } = require('../cache/redis');
|
const { cacheGet, cacheSet } = require('../cache/redis');
|
||||||
const { logApiCall } = require('../services/analytics');
|
const { logApiCall } = require('../services/analytics');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch pincode from India Post API and save to database
|
||||||
|
*/
|
||||||
|
async function fetchFromIndiaPostAPI(pincode) {
|
||||||
|
try {
|
||||||
|
console.log(`🌐 Fetching pincode ${pincode} from India Post API...`);
|
||||||
|
|
||||||
|
const response = await axios.get(`https://api.postalpincode.in/pincode/${pincode}`, {
|
||||||
|
timeout: 10000
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data[0] && response.data[0].Status === 'Success') {
|
||||||
|
const postOffices = response.data[0].PostOffice;
|
||||||
|
const savedRecords = [];
|
||||||
|
|
||||||
|
for (const po of postOffices) {
|
||||||
|
try {
|
||||||
|
// Save to database for future lookups (UPSERT for self-healing)
|
||||||
|
await query(
|
||||||
|
`INSERT INTO pincodes (pincode, office_name, office_type, district, division, region, state, block, latitude, longitude, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())
|
||||||
|
ON CONFLICT (id)
|
||||||
|
DO UPDATE SET
|
||||||
|
office_name = EXCLUDED.office_name,
|
||||||
|
office_type = EXCLUDED.office_type,
|
||||||
|
district = EXCLUDED.district,
|
||||||
|
division = EXCLUDED.division,
|
||||||
|
region = EXCLUDED.region,
|
||||||
|
state = EXCLUDED.state,
|
||||||
|
block = EXCLUDED.block,
|
||||||
|
latitude = EXCLUDED.latitude,
|
||||||
|
longitude = EXCLUDED.longitude,
|
||||||
|
updated_at = NOW()`,
|
||||||
|
[
|
||||||
|
pincode,
|
||||||
|
po.Name || '',
|
||||||
|
po.BranchType || 'PO',
|
||||||
|
po.District || '',
|
||||||
|
po.Division || '',
|
||||||
|
po.Region || '',
|
||||||
|
po.State || '',
|
||||||
|
po.Block || '',
|
||||||
|
'0',
|
||||||
|
'0'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} catch (insertError) {
|
||||||
|
// Try to update if insert fails
|
||||||
|
try {
|
||||||
|
await query(
|
||||||
|
`UPDATE pincodes
|
||||||
|
SET office_type = $3, district = $4, division = $5, region = $6, state = $7, block = $8, updated_at = NOW()
|
||||||
|
WHERE pincode = $1 AND office_name = $2`,
|
||||||
|
[
|
||||||
|
pincode,
|
||||||
|
po.Name || '',
|
||||||
|
po.BranchType || 'PO',
|
||||||
|
po.District || '',
|
||||||
|
po.Division || '',
|
||||||
|
po.Region || '',
|
||||||
|
po.State || '',
|
||||||
|
po.Block || ''
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} catch (updateError) {
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
savedRecords.push({
|
||||||
|
pincode: pincode,
|
||||||
|
office_name: po.Name || '',
|
||||||
|
office_type: po.BranchType || 'PO',
|
||||||
|
district: po.District || '',
|
||||||
|
division: po.Division || '',
|
||||||
|
region: po.Region || '',
|
||||||
|
state: po.State || '',
|
||||||
|
block: po.Block || '',
|
||||||
|
latitude: '0',
|
||||||
|
longitude: '0'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Fetched and saved ${savedRecords.length} offices for pincode ${pincode}`);
|
||||||
|
return { success: true, data: savedRecords, source: 'INDIA_POST_API' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, error: 'Invalid pincode' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ India Post API error for ${pincode}:`, error.message);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const STATE_CODES = {
|
const STATE_CODES = {
|
||||||
'Andhra Pradesh': 'AP', 'Arunachal Pradesh': 'AR', 'Assam': 'AS', 'Bihar': 'BR',
|
'Andhra Pradesh': 'AP', 'Arunachal Pradesh': 'AR', 'Assam': 'AS', 'Bihar': 'BR',
|
||||||
'Chhattisgarh': 'CG', 'Delhi': 'DL', 'Goa': 'GA', 'Gujarat': 'GJ', 'Haryana': 'HR',
|
'Chhattisgarh': 'CG', 'Delhi': 'DL', 'Goa': 'GA', 'Gujarat': 'GJ', 'Haryana': 'HR',
|
||||||
@ -112,11 +208,12 @@ router.get('/search', async (req, res, next) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /v1/pincode/:pincode
|
* GET /v1/pincode/:pincode
|
||||||
* Lookup pincode details
|
* Lookup pincode details with India Post API fallback
|
||||||
*/
|
*/
|
||||||
router.get('/:pincode', async (req, res, next) => {
|
router.get('/:pincode', async (req, res, next) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let success = false;
|
let success = false;
|
||||||
|
let source = 'LOCAL_DB';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { pincode } = req.params;
|
const { pincode } = req.params;
|
||||||
@ -130,18 +227,55 @@ router.get('/:pincode', async (req, res, next) => {
|
|||||||
|
|
||||||
const cacheKey = `pincode:${pincode}`;
|
const cacheKey = `pincode:${pincode}`;
|
||||||
let data = await cacheGet(cacheKey);
|
let data = await cacheGet(cacheKey);
|
||||||
|
let needsRefresh = false;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
source = 'CACHE';
|
||||||
|
// Self-healing: Check if taluk is missing
|
||||||
|
if (data.length > 0 && (!data[0].block || data[0].block === '')) {
|
||||||
|
console.log(`🔄 Self-healing: Taluk missing for pincode ${pincode}, refetching...`);
|
||||||
|
needsRefresh = true;
|
||||||
|
data = null; // Force refetch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
// Try local database first
|
||||||
const result = await query('SELECT * FROM pincodes WHERE pincode = $1', [pincode]);
|
const result = await query('SELECT * FROM pincodes WHERE pincode = $1', [pincode]);
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
if (result.rows.length === 0 || needsRefresh) {
|
||||||
return res.status(404).json({
|
// Fallback to India Post API (or forced refresh for self-healing)
|
||||||
success: false,
|
if (needsRefresh) {
|
||||||
error: { code: 'PINCODE_NOT_FOUND', message: 'Pincode not found' }
|
console.log(`🔄 Refreshing pincode ${pincode} with India Post API...`);
|
||||||
});
|
} else {
|
||||||
|
console.log(`📦 Pincode ${pincode} not in DB, trying India Post API...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiResult = await fetchFromIndiaPostAPI(pincode);
|
||||||
|
|
||||||
|
if (!apiResult.success) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: { code: 'PINCODE_NOT_FOUND', message: 'Pincode not found in any source' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
data = apiResult.data;
|
||||||
|
source = 'INDIA_POST_API';
|
||||||
|
} else {
|
||||||
|
data = result.rows;
|
||||||
|
// Double-check: if DB data also has empty taluk, force refresh
|
||||||
|
if (data.length > 0 && (!data[0].block || data[0].block === '')) {
|
||||||
|
console.log(`🔄 Self-healing: DB data also missing taluk for ${pincode}, refetching...`);
|
||||||
|
const apiResult = await fetchFromIndiaPostAPI(pincode);
|
||||||
|
|
||||||
|
if (apiResult.success) {
|
||||||
|
data = apiResult.data;
|
||||||
|
source = 'INDIA_POST_API';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data = result.rows;
|
|
||||||
await cacheSet(cacheKey, data, 604800); // Cache for 7 days
|
await cacheSet(cacheKey, data, 604800); // Cache for 7 days
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,19 +286,17 @@ router.get('/:pincode', async (req, res, next) => {
|
|||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
pincode,
|
pincode,
|
||||||
locations: data.map(row => ({
|
source,
|
||||||
office_name: row.office_name,
|
district: primary.district,
|
||||||
office_type: row.office_type,
|
state: primary.state,
|
||||||
district: row.district,
|
state_code: STATE_CODES[primary.state] || '',
|
||||||
state: row.state,
|
latitude: parseFloat(primary.latitude) || null,
|
||||||
latitude: parseFloat(row.latitude) || null,
|
longitude: parseFloat(primary.longitude) || null,
|
||||||
longitude: parseFloat(row.longitude) || null
|
offices: data.map(office => ({
|
||||||
})),
|
name: office.office_name,
|
||||||
primary: {
|
type: office.office_type,
|
||||||
district: primary.district,
|
taluk: office.block || ''
|
||||||
state: primary.state,
|
}))
|
||||||
state_code: STATE_CODES[primary.state] || ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
request_id: `req_pin_${Date.now()}`,
|
request_id: `req_pin_${Date.now()}`,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user