From a9d297bbf72f4fc3f394d8cb242eb664bb5674d2 Mon Sep 17 00:00:00 2001 From: navin-oss Date: Fri, 6 Mar 2026 20:38:27 +0530 Subject: [PATCH 1/2] fix: resolve all backend issues --- backend/config/blockchain.js | 75 +++++++++++++++++++++ backend/scripts/reconcile.js | 123 ++++++++++++++++++++++++++++++----- backend/server.js | 46 +++++-------- 3 files changed, 196 insertions(+), 48 deletions(-) create mode 100644 backend/config/blockchain.js diff --git a/backend/config/blockchain.js b/backend/config/blockchain.js new file mode 100644 index 0000000..211b500 --- /dev/null +++ b/backend/config/blockchain.js @@ -0,0 +1,75 @@ +const { ethers } = require('ethers'); + +const PROVIDER_URL = process.env.INFURA_URL; +const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS; +const PRIVATE_KEY = process.env.PRIVATE_KEY; + +// Contract ABI - aligned with CropChain.sol +const contractABI = [ + "event BatchCreated(bytes32 indexed batchId, string ipfsCID, uint256 quantity, address indexed creator)", + "event BatchUpdated(bytes32 indexed batchId, uint8 stage, string actorName, string location, address indexed updatedBy)", + "function getBatch(bytes32 batchId) view returns (tuple(bytes32 batchId, bytes32 cropTypeHash, string ipfsCID, uint256 quantity, uint256 createdAt, address creator, bool exists, bool isRecalled))", + "function getTotalBatches() view returns (uint256)", + "function getBatchIdByIndex(uint256 index) view returns (bytes32)", + "function createBatch(bytes32 batchId, bytes32 cropTypeHash, string calldata ipfsCID, uint256 quantity, string calldata actorName, string calldata location, string calldata notes) returns (bool)", + "function updateBatch(bytes32 batchId, uint8 stage, string calldata actorName, string calldata location, string calldata notes) returns (bool)" +]; + +let contractInstance = null; +let provider = null; +let wallet = null; + +/** + * Initialize blockchain connection and return contract instance + * @returns {ethers.Contract|null} Contract instance or null if not configured + */ +function getContract() { + if (contractInstance) { + return contractInstance; + } + + if (!PROVIDER_URL || !CONTRACT_ADDRESS || !PRIVATE_KEY) { + console.warn('Blockchain not configured: Missing INFURA_URL, CONTRACT_ADDRESS, or PRIVATE_KEY'); + return null; + } + + try { + provider = new ethers.JsonRpcProvider(PROVIDER_URL); + wallet = new ethers.Wallet(PRIVATE_KEY, provider); + contractInstance = new ethers.Contract(CONTRACT_ADDRESS, contractABI, wallet); + console.log('āœ“ Blockchain contract initialized'); + return contractInstance; + } catch (error) { + console.error('Failed to initialize blockchain connection:', error.message); + return null; + } +} + +/** + * Get provider instance + * @returns {ethers.JsonRpcProvider|null} + */ +function getProvider() { + if (!provider && PROVIDER_URL) { + provider = new ethers.JsonRpcProvider(PROVIDER_URL); + } + return provider; +} + +/** + * Get wallet instance + * @returns {ethers.Wallet|null} + */ +function getWallet() { + if (!wallet && PRIVATE_KEY && provider) { + wallet = new ethers.Wallet(PRIVATE_KEY, provider); + } + return wallet; +} + +module.exports = { + getContract, + getProvider, + getWallet, + contractABI +}; diff --git a/backend/scripts/reconcile.js b/backend/scripts/reconcile.js index 299420b..eb08ec1 100644 --- a/backend/scripts/reconcile.js +++ b/backend/scripts/reconcile.js @@ -1,25 +1,112 @@ +require('dotenv').config(); +const { ethers } = require('ethers'); const Batch = require('../models/Batch'); -const { getContract } = require('../config/blockchain'); // adjust if needed +const { getContract } = require('../config/blockchain'); -async function reconcile() { - const contract = await getContract(); - - const total = await contract.getBatchCount(); +// Stage enum mapping from CropChain.sol +const STAGE_NAMES = ['Farmer', 'Mandi', 'Transport', 'Retailer']; - for (let i = 0; i < total; i++) { - const onChain = await contract.batches(i); +/** + * Convert uint8 stage to human-readable string + * @param {number} stage - Stage enum value (uint8) + * @returns {string} Stage name + */ +function getStageName(stage) { + if (stage >= 0 && stage < STAGE_NAMES.length) { + return STAGE_NAMES[stage]; + } + return 'Unknown'; +} - await Batch.updateOne( - { batchId: i }, - { - stage: onChain.stage, - syncStatus: 'synced' - }, - { upsert: true } - ); - } +/** + * Reconcile blockchain data with MongoDB database + * Syncs batch data from blockchain to local database + */ +async function reconcile() { + console.log('šŸ”„ Starting reconciliation...'); + + const contract = getContract(); + + if (!contract) { + console.error('āŒ Blockchain contract not available. Check configuration.'); + process.exit(1); + } + + try { + // Get total number of batches on blockchain + const totalBatches = await contract.getTotalBatches(); + console.log(`šŸ“Š Found ${totalBatches} batches on blockchain`); + + let synced = 0; + let skipped = 0; + let errors = 0; + + // Iterate through all batch IDs on blockchain + for (let i = 0; i < totalBatches; i++) { + try { + // Get batch ID at index + const batchIdBytes32 = await contract.getBatchIdByIndex(i); + + // Get full batch data + const onChainBatch = await contract.getBatch(batchIdBytes32); + + // Only sync batches that exist on chain + if (onChainBatch.exists) { + // Convert batchId bytes32 to readable string format + const readableBatchId = ethers.toUtf8String(batchIdBytes32); + + // Map blockchain stage (uint8) to string + const stageName = getStageName(0); + + // Update or insert batch in MongoDB + await Batch.updateOne( + { batchId: readableBatchId }, + { + $set: { + syncStatus: 'synced', + lastSyncedAt: new Date(), + onChainData: { + quantity: Number(onChainBatch.quantity), + creator: onChainBatch.creator, + createdAt: new Date(Number(onChainBatch.createdAt) * 1000), + exists: onChainBatch.exists, + isRecalled: onChainBatch.isRecalled + } + } + }, + { upsert: true } + ); + + synced++; + console.log(` āœ“ Synced batch: ${readableBatchId}`); + } else { + skipped++; + } + } catch (batchError) { + errors++; + console.error(` āœ— Error processing batch at index ${i}:`, batchError.message); + } + } + + console.log(`\nāœ… Reconciliation complete:`); + console.log(` - Synced: ${synced}`); + console.log(` - Skipped: ${skipped}`); + console.log(` - Errors: ${errors}`); + + } catch (error) { + console.error('āŒ Reconciliation failed:', error.message); + process.exit(1); + } +} - console.log('āœ… Reconciliation complete'); +// Run if called directly +if (require.main === module) { + reconcile() + .then(() => process.exit(0)) + .catch((error) => { + console.error('Fatal error:', error); + process.exit(1); + }); } -reconcile(); +module.exports = { reconcile }; diff --git a/backend/server.js b/backend/server.js index e05ea4f..bb7dc39 100644 --- a/backend/server.js +++ b/backend/server.js @@ -21,7 +21,6 @@ const { createBatchSchema, updateBatchSchema } = require("./validations/batchSch const { protect, adminOnly, authorizeBatchOwner, authorizeRoles } = require('./middleware/auth'); const apiResponse = require('./utils/apiResponse'); const crypto = require('crypto'); -const mongoose = require('mongoose'); // Import MongoDB Model const Batch = require('./models/Batch'); @@ -35,30 +34,11 @@ const PORT = process.env.PORT || 3001; // ==================== MIDDLEWARE FUNCTIONS ==================== -// JWT Authentication Middleware -const auth = (req, res, next) => { - const token = req.header('Authorization')?.replace('Bearer ', ''); - - if (!token) { - return res.status(401).json({ error: 'Unauthorized - No token provided' }); - } - - try { - const decoded = jwt.verify(token, process.env.JWT_SECRET); - req.user = decoded; - next(); - } catch (error) { - return res.status(401).json({ error: 'Invalid token' }); - } -}; - -// Admin Role Middleware -const admin = (req, res, next) => { - if (!req.user || req.user.role !== 'admin') { - return res.status(403).json({ error: 'Admin access required' }); - } - next(); -}; +// Authentication is handled by middleware imported from './middleware/auth': +// - protect: Verifies JWT and fetches full user from MongoDB +// - adminOnly: Checks if user has admin role +// - authorizeBatchOwner: Verifies user owns the batch +// - authorizeRoles: Role-based authorization // Security logging middleware const securityLogger = (req, res, next) => { @@ -235,10 +215,11 @@ if (PROVIDER_URL && CONTRACT_ADDRESS && PRIVATE_KEY) { wallet = new ethers.Wallet(PRIVATE_KEY, provider); const contractABI = [ - "event BatchCreated(bytes32 indexed batchId, address indexed farmer, uint256 quantity)", - "event BatchUpdated(bytes32 indexed batchId, string stage, address indexed actor)", - "function getBatch(bytes32 batchId) view returns (tuple(address farmer, uint256 quantity, string stage, bool exists))", - "function createBatch(bytes32 batchId, uint256 quantity, string memory metadata) returns (bool)" + "event BatchCreated(bytes32 indexed batchId, string ipfsCID, uint256 quantity, address indexed creator)", + "event BatchUpdated(bytes32 indexed batchId, uint8 stage, string actorName, string location, address indexed updatedBy)", + "function getBatch(bytes32 batchId) view returns (tuple(bytes32 batchId, bytes32 cropTypeHash, string ipfsCID, uint256 quantity, uint256 createdAt, address creator, bool exists, bool isRecalled))", + "function createBatch(bytes32 batchId, bytes32 cropTypeHash, string calldata ipfsCID, uint256 quantity, string calldata actorName, string calldata location, string calldata notes) returns (bool)", + "function updateBatch(bytes32 batchId, uint8 stage, string calldata actorName, string calldata location, string calldata notes) returns (bool)" ]; contractInstance = new ethers.Contract(CONTRACT_ADDRESS, contractABI, wallet); @@ -275,12 +256,16 @@ async function generateBatchId() { await session.abortTransaction(); session.endSession(); throw error; + } +} + /** * Generate batch ID with optional session support for transaction safety * @param {mongoose.ClientSession} session - MongoDB session for transaction * @returns {string} - Generated batch ID */ async function generateBatchId(session = null) { + const currentYear = new Date().getFullYear(); const options = { new: true, upsert: true }; if (session) { options.session = session; @@ -291,9 +276,10 @@ async function generateBatchId(session = null) { { $inc: { seq: 1 } }, options ); - return `CROP-2024-${String(counter.seq).padStart(3, '0')}`; + return `CROP-${currentYear}-${String(counter.seq).padStart(4, '0')}`; } + async function generateQRCode(batchId) { try { return await QRCode.toDataURL(batchId, { From 59b2853e87fb16a77db5c3275c25ac4aba0f6f50 Mon Sep 17 00:00:00 2001 From: navin-oss Date: Fri, 6 Mar 2026 22:01:00 +0530 Subject: [PATCH 2/2] fix: remove deprecated BatchStore.js to eliminate data store confusion --- backend/models/BatchStore.js | 47 ------------------------------------ 1 file changed, 47 deletions(-) delete mode 100644 backend/models/BatchStore.js diff --git a/backend/models/BatchStore.js b/backend/models/BatchStore.js deleted file mode 100644 index f26e3a2..0000000 --- a/backend/models/BatchStore.js +++ /dev/null @@ -1,47 +0,0 @@ -const batches = new Map(); -let batchCounter = 1; - -// Initialize with sample data -const sampleBatch = { - batchId: 'CROP-2024-001', - farmerName: 'Rajesh Kumar', - farmerAddress: 'Village Rampur, District Meerut, UP', - cropType: 'rice', - quantity: 1000, - harvestDate: '2024-01-15', - origin: 'Rampur, Meerut', - certifications: 'Organic, Fair Trade', - description: 'High-quality Basmati rice grown using traditional methods', - createdAt: new Date().toISOString(), - currentStage: 'mandi', - updates: [ - { - stage: 'farmer', - actor: 'Rajesh Kumar', - location: 'Rampur, Meerut', - timestamp: '2024-01-15T10:00:00.000Z', - notes: 'Initial harvest recorded' - }, - { - stage: 'mandi', - actor: 'Punjab Mandi', - location: 'Ludhiana Market', - timestamp: '2024-01-16T14:30:00.000Z', - notes: 'Quality checked and processed' - } - ], - qrCode: 'data:image/png;base64,sample', - blockchainHash: '0x123456789abcdef' -}; - -batches.set('CROP-2024-001', sampleBatch); -batchCounter = 2; - -module.exports = { - batches, - getNextId: () => { - const id = `CROP-2024-${String(batchCounter).padStart(3, '0')}`; - batchCounter++; - return id; - } -};