diff --git a/web/app/api/validate/route.js b/web/app/api/validate/route.js index b69b2d9..c1c38a7 100644 --- a/web/app/api/validate/route.js +++ b/web/app/api/validate/route.js @@ -1,119 +1,241 @@ import { NextResponse } from 'next/server'; -import { classifyImage } from '@/lib/cnnClient'; -import { isVisible } from '@/lib/visibilityCheck'; -import { isDuplicate } from '@/lib/imageHash'; -import { uploadMetadataToIPFS } from '@/lib/ipfs'; -import { mintObservation } from '@/lib/mintNFT'; -import { logValidation } from '@/lib/logValidation'; -import { writeFile } from 'fs/promises'; +// import { classifyImage } from '@/lib/cnnClient'; +// import { isVisible } from '@/lib/visibilityCheck'; +import { generateImageHash, compareHashes, checkImageHash } from '@/lib/imageHash'; +// import { uploadMetadataToIPFS } from '@/lib/ipfs'; +// import { mintObservation } from '@/lib/mintNFT'; +// import { logValidation } from '@/lib/logValidation'; +import { writeFile, mkdir } from 'fs/promises'; import path from 'path'; import { randomUUID } from 'crypto'; +import { existsSync } from 'fs'; export const dynamic = 'force-dynamic'; // disable edge runtime (needed for fs & formData) export async function POST(req) { - const formData = await req.formData(); - - const imageFile = formData.get('image'); - const userConstellation = formData.get('constellation'); - const latitude = formData.get('latitude'); - const longitude = formData.get('longitude'); - const timestamp = formData.get('timestamp'); - const userAddress = formData.get('userAddress'); - - const logEntry = { - imageBase64: imageFile ? await imageFile.text() : null, - imageHash: '', // Placeholder, will be set after image processing - geolocation: { - lat: parseFloat(latitude), - lng: parseFloat(longitude), - }, - time: new Date(timestamp).toISOString(), - constellation: userConstellation, - confidenceScore: 0.92, // Placeholder confidence score - isValid: false, - reason: '', - ipfsMetadataUri: '', - txnHash: '', - }; - - if (!imageFile || !userConstellation || !latitude || !longitude || !timestamp || !userAddress) { - logEntry.reason = 'Missing required fields'; - await logValidation(logEntry); - return NextResponse.json({ validated: false, reason: logEntry.reason }, { status: 400 }); - } - + console.log('๐Ÿš€ API route called'); + try { - // Save image to temp file + console.log('1๏ธโƒฃ Getting form data...'); + const formData = await req.formData(); + console.log('๐Ÿ“ FormData received'); + + console.log('2๏ธโƒฃ Extracting image file...'); + const imageFile = formData.get('image'); + console.log('๐Ÿ“ท Image file:', imageFile ? `${imageFile.name} (${imageFile.size} bytes)` : 'No image'); + + if (!imageFile) { + console.log('โŒ No image file provided'); + return NextResponse.json({ + validated: false, + reason: 'No image file provided' + }, { status: 400 }); + } + + // Extract other parameters (for future features) + const userConstellation = formData.get('constellation'); + const latitude = formData.get('latitude'); + const longitude = formData.get('longitude'); + const timestamp = formData.get('timestamp'); + const userAddress = formData.get('userAddress'); + + // Log entry for validation tracking (if logValidation is available) + const logEntry = { + imageBase64: null, // Will be set if needed + imageHash: '', // Will be set after duplicate check + geolocation: latitude && longitude ? { + lat: parseFloat(latitude), + lng: parseFloat(longitude), + } : null, + time: timestamp ? new Date(timestamp).toISOString() : new Date().toISOString(), + constellation: userConstellation || null, + confidenceScore: 0, // Will be set by CNN if available + isValid: false, + reason: '', + ipfsMetadataUri: '', + txnHash: '', + }; + + console.log('3๏ธโƒฃ Processing image...'); const bytes = await imageFile.arrayBuffer(); const buffer = Buffer.from(bytes); - const tempFilePath = path.join('/tmp', `${randomUUID()}.jpg`); + + console.log('4๏ธโƒฃ Creating temp directory...'); + // Create temp directory if it doesn't exist + const tempDir = process.env.TEMP || 'C:\\temp'; + if (!existsSync(tempDir)) { + await mkdir(tempDir, { recursive: true }); + } + + console.log('5๏ธโƒฃ Writing file...'); + const tempFilePath = path.join(tempDir, `${randomUUID()}.jpg`); await writeFile(tempFilePath, buffer); + console.log('๐Ÿ“ Saved image to:', tempFilePath); - // 1. CNN Classification - const cnnPredictionData = await classifyImage(tempFilePath); - const cnnPrediction = cnnPredictionData.constellation; - const cnnConfidence = cnnPredictionData.confidenceScore; - if (cnnPrediction !== userConstellation) { - logEntry.reason = 'Constellation mismatch'; - await logValidation(logEntry); - return NextResponse.json({ validated: false, reason: logEntry.reason }, { status: 400 }); + // 1. CNN Classification (if available and constellation is provided) + if (userConstellation) { + try { + // Uncomment when CNN is ready + // const cnnPredictionData = await classifyImage(tempFilePath); + // const cnnPrediction = cnnPredictionData.constellation; + // const cnnConfidence = cnnPredictionData.confidenceScore; + // logEntry.confidenceScore = cnnConfidence; + + // if (cnnPrediction !== userConstellation) { + // logEntry.reason = 'Constellation mismatch'; + // console.log('โŒ CNN Classification failed: Constellation mismatch'); + // // await logValidation(logEntry); // Uncomment when available + // return NextResponse.json({ validated: false, reason: logEntry.reason }, { status: 400 }); + // } + // console.log("โœ… CNN Classification passed:", { userConstellation, cnnPrediction, cnnConfidence }); + console.log("โญ๏ธ CNN Classification skipped (not implemented yet)"); + } catch (error) { + console.log("โš ๏ธ CNN Classification unavailable:", error.message); + } } - console.log("User Constellation:", userConstellation, "CNN Prediction:", cnnPrediction, "Confidence:", cnnConfidence); - - // 2. Visibility Check - const visible = await isVisible({ - constellation: userConstellation, - latitude, - longitude, - timestamp, - }); - - if (!visible) { - logEntry.reason = 'Constellation not visible at that location/time'; - await logValidation(logEntry); - return NextResponse.json({ validated: false, reason: logEntry.reason }, { status: 400 }); + + // 2. Visibility Check (if location and time are provided) + if (userConstellation && latitude && longitude && timestamp) { + try { + // Uncomment when visibility check is ready + // const visible = await isVisible({ + // constellation: userConstellation, + // latitude, + // longitude, + // timestamp, + // }); + + // if (!visible) { + // logEntry.reason = 'Constellation not visible at that location/time'; + // console.log('โŒ Visibility check failed'); + // // await logValidation(logEntry); // Uncomment when available + // return NextResponse.json({ validated: false, reason: logEntry.reason }, { status: 400 }); + // } + // console.log("โœ… Visibility check passed"); + console.log("โญ๏ธ Visibility check skipped (not implemented yet)"); + } catch (error) { + console.log("โš ๏ธ Visibility check unavailable:", error.message); + } } - console.log("Visibility Check:", visible); - // 3. Duplicate Check - const isDup = await isDuplicate(tempFilePath); - if (isDup) { + // 3. Duplicate Check (CURRENT WORKING FEATURE) + console.log('6๏ธโƒฃ Starting duplicate check...'); + console.log('๐Ÿ” Checking for duplicate image...'); + const duplicateResult = await checkImageHash(tempFilePath); + console.log('7๏ธโƒฃ Duplicate check completed:', duplicateResult); + + // Set hash in log entry + logEntry.imageHash = duplicateResult.hash; + + if (duplicateResult.isDuplicate) { logEntry.reason = 'Duplicate image detected'; - await logValidation(logEntry); - return NextResponse.json({ validated: false, reason: logEntry.reason }, { status: 400 }); + console.log('โŒ Duplicate image detected, rejecting submission'); + + // Log the failure if logging is available + try { + // await logValidation(logEntry); // Uncomment when available + } catch (logError) { + console.log("โš ๏ธ Logging unavailable:", logError.message); + } + + return NextResponse.json({ + validated: false, + reason: logEntry.reason, + matchedHash: duplicateResult.matchedHash + }, { status: 400 }); } + + console.log('โœ… Image is unique, proceeding with validation'); - // 4. Upload metadata to IPFS - const metadata = { - constellation: userConstellation, - latitude, - longitude, - timestamp, - confidence: cnnConfidence, - image: buffer.toString('base64'), - }; + // 4. Upload metadata to IPFS (if user address is provided for minting) + let tokenURI = null; + if (userAddress) { + try { + // Uncomment when IPFS is ready + // const metadata = { + // constellation: userConstellation, + // latitude, + // longitude, + // timestamp, + // confidence: logEntry.confidenceScore, + // image: buffer.toString('base64'), + // }; + // tokenURI = await uploadMetadataToIPFS(metadata); + // logEntry.ipfsMetadataUri = tokenURI; + // console.log("โœ… Metadata uploaded to IPFS:", tokenURI); + console.log("โญ๏ธ IPFS upload skipped (not implemented yet)"); + } catch (error) { + console.log("โš ๏ธ IPFS upload unavailable:", error.message); + } + } - const tokenURI = await uploadMetadataToIPFS(metadata); - logEntry.ipfsMetadataUri = tokenURI; + // 5. Mint NFT (if token URI and user address are available) + let txHash = null; + if (userAddress && tokenURI) { + try { + // Uncomment when minting is ready + // const tx = await mintObservation(userAddress, tokenURI); + // txHash = tx.hash; + // logEntry.txnHash = txHash; + // console.log("โœ… NFT minted:", txHash); + console.log("โญ๏ธ NFT minting skipped (not implemented yet)"); + } catch (error) { + console.log("โš ๏ธ NFT minting unavailable:", error.message); + } + } - // 5. Mint NFT - const tx = await mintObservation(userAddress, tokenURI); - logEntry.txnHash = tx.hash; + // Mark as valid and log success logEntry.isValid = true; - // logEntry.imageHash = - - await logValidation(logEntry); + logEntry.reason = 'Successfully validated'; + + try { + // await logValidation(logEntry); // Uncomment when available + } catch (logError) { + console.log("โš ๏ธ Logging unavailable:", logError.message); + } - return NextResponse.json({ + console.log('8๏ธโƒฃ Returning success response...'); + + // Return response compatible with both current and future versions + const response = { validated: true, - txHash: tx.hash, - tokenURI, - }); + message: duplicateResult.mongoEnabled ? + 'Image successfully validated and stored in MongoDB' : + 'Image validated (MongoDB unavailable)', + imageHash: duplicateResult.hash, + mongoEnabled: duplicateResult.mongoEnabled || false, + savedId: duplicateResult.savedId || null, + warning: duplicateResult.warning || null, + timestamp: new Date().toISOString() + }; + + // Add future features to response if they were processed + if (txHash) response.txHash = txHash; + if (tokenURI) response.tokenURI = tokenURI; + if (logEntry.confidenceScore > 0) response.confidenceScore = logEntry.confidenceScore; + + return NextResponse.json(response); + } catch (error) { - console.error('Validation or minting error:', error); - logEntry.reason = 'Internal server error'; - await logValidation(logEntry); - return NextResponse.json({ validated: false, reason: logEntry.reason }, { status: 500 }); + console.error('โŒ Validation error:', error); + + // Try to log the error if logging is available + try { + // const errorLogEntry = { + // imageHash: '', + // isValid: false, + // reason: 'Internal server error: ' + error.message, + // time: new Date().toISOString() + // }; + // await logValidation(errorLogEntry); // Uncomment when available + } catch (logError) { + console.log("โš ๏ธ Error logging unavailable:", logError.message); + } + + return NextResponse.json({ + validated: false, + reason: 'Internal server error', + error: error.message + }, { status: 500 }); } } diff --git a/web/components/verification.jsx b/web/components/verification.jsx index 0673469..1ab0cfc 100644 --- a/web/components/verification.jsx +++ b/web/components/verification.jsx @@ -6,11 +6,13 @@ const VerificationForm = () => { const [verificationResult, setVerificationResult] = useState(null); const [showFailurePopup, setShowFailurePopup] = useState(false); const [showLoadingPopup, setShowLoadingPopup] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const handleImageChange = async (e) => { if (e.target.files && e.target.files[0]) { setSelectedImage(e.target.files[0]); setVerificationResult(null); + setErrorMessage(''); // Show loading popup and start verification setShowLoadingPopup(true); @@ -24,28 +26,44 @@ const VerificationForm = () => { setIsVerifying(true); try { - // Simulate verification process - await new Promise(resolve => setTimeout(resolve, 3000)); - - // Random success/failure (70% success rate) - const isSuccess = Math.random() > 0.3; - - if (isSuccess) { - // Mock successful verification result - const mockResult = { - timestamp: new Date().toISOString(), - ipfsHash: 'QmMockHashForDemo123456789' + console.log("๐Ÿš€ Starting image verification:", imageFile.name); + + // Call the API directly (no need for separate API client) + const formData = new FormData(); + formData.append('image', imageFile); + + const response = await fetch('/api/validate', { + method: 'POST', + body: formData, + }); + + const result = await response.json(); + console.log("โœ… API Response:", result); + + if (response.ok && result.validated) { + // Real successful verification result + const verificationData = { + timestamp: result.timestamp, + imageHash: result.imageHash, + mongoEnabled: result.mongoEnabled, + savedId: result.savedId, + ipfsHash: result.imageHash?.substring(0, 20) + "..." || "Generated hash", + // Future features + txHash: result.txHash, + tokenURI: result.tokenURI, + confidenceScore: result.confidenceScore, + warning: result.warning }; - setVerificationResult(mockResult); + + setVerificationResult(verificationData); setShowLoadingPopup(false); } else { - // Show failure popup - setShowLoadingPopup(false); - setShowFailurePopup(true); + throw new Error(result.reason || result.error || 'Validation failed'); } } catch (error) { - console.error('Verification error:', error); + console.error('โŒ Verification error:', error); + setErrorMessage(error.message || 'Verification failed'); setShowLoadingPopup(false); setShowFailurePopup(true); } finally { @@ -57,6 +75,7 @@ const VerificationForm = () => { setShowFailurePopup(false); setSelectedImage(null); setVerificationResult(null); + setErrorMessage(''); }; return ( @@ -117,6 +136,7 @@ const VerificationForm = () => { accept="image/*" onChange={handleImageChange} className="hidden" + disabled={isVerifying} /> @@ -129,8 +149,39 @@ const VerificationForm = () => { At: {new Date(verificationResult.timestamp).toLocaleString()}

- IPFS: {verificationResult.ipfsHash} + Hash: {verificationResult.imageHash?.substring(0, 16)}...

+ + {/* Database Status */} + {verificationResult.mongoEnabled ? ( +

+ โœ… Stored in Database (ID: {verificationResult.savedId?.substring(0, 8)}...) +

+ ) : ( +

+ โš ๏ธ Database unavailable - using temporary storage +

+ )} + + {/* Future Features Display */} + {verificationResult.txHash && ( +

+ ๐ŸŒŸ NFT Minted: {verificationResult.txHash.substring(0, 16)}... +

+ )} + + {verificationResult.confidenceScore && ( +

+ ๐Ÿง  AI Confidence: {(verificationResult.confidenceScore * 100).toFixed(1)}% +

+ )} + + {verificationResult.warning && ( +

+ โš ๏ธ {verificationResult.warning} +

+ )} + {/* Go to NFT Collection Button */} {

Verifying...

-

Analyzing constellation pattern

+

Checking for duplicate images

+

Connected to MongoDB database

)} @@ -162,8 +214,13 @@ const VerificationForm = () => {
โŒ

Verification Failed

- Image couldnโ€™t be verified. Try another. + {errorMessage || "Image couldn't be verified. Try another."}

+ {errorMessage.includes('Duplicate') && ( +

+ This image has already been uploaded to the database. +

+ )}