This commit implements a complete blockchain-based plant tracking system that preserves lineage across clones, seeds, and all plant offspring while connecting growers through geographic proximity. Features implemented: - Custom blockchain with proof-of-work consensus - Plant registration and cloning with lineage tracking - Geographic discovery to find nearby plants and growers - Integration with plants.net API for plant identification - Comprehensive web UI for plant management - RESTful API endpoints for all operations - Network statistics and visualization Core Components: - lib/blockchain/: PlantBlock, PlantChain, and blockchain manager - lib/services/: plants.net API and geolocation services - pages/api/plants/: REST API endpoints for all operations - pages/: Frontend UI pages for registration, exploration, and lineage Technical Details: - TypeScript for type safety - Next.js for server-side rendering - Tailwind CSS for responsive design - JSON file-based blockchain storage - Haversine distance calculations for geolocation - OpenStreetMap integration for geocoding This system enables large-scale adoption by: - Making plant lineage tracking accessible to everyone - Connecting local communities through plant sharing - Providing immutable proof of plant provenance - Supporting unlimited generations of plant propagation - Scaling from individual growers to global networks Documentation includes comprehensive README with: - Quick start guide - API reference - Architecture details - Scaling recommendations - Use cases for various audiences - Roadmap for future enhancements
87 lines
2.4 KiB
TypeScript
87 lines
2.4 KiB
TypeScript
/**
|
|
* API Route: Search for plants
|
|
* GET /api/plants/search?q=tomato&type=species
|
|
*/
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { getBlockchain } from '../../../lib/blockchain/manager';
|
|
import { getPlantsNetService } from '../../../lib/services/plantsnet';
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
if (req.method !== 'GET') {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
try {
|
|
const { q, type } = req.query;
|
|
|
|
if (!q || typeof q !== 'string') {
|
|
return res.status(400).json({ error: 'Missing search query parameter: q' });
|
|
}
|
|
|
|
const blockchain = getBlockchain();
|
|
const searchTerm = q.toLowerCase();
|
|
|
|
// Search in blockchain
|
|
const allPlants = Array.from(
|
|
new Set(blockchain.chain.map(block => block.plant.id))
|
|
).map(id => blockchain.getPlant(id)!);
|
|
|
|
let results = allPlants.filter(plant => {
|
|
if (!plant) return false;
|
|
|
|
const searchIn = [
|
|
plant.commonName?.toLowerCase(),
|
|
plant.scientificName?.toLowerCase(),
|
|
plant.genus?.toLowerCase(),
|
|
plant.family?.toLowerCase(),
|
|
plant.owner.name?.toLowerCase(),
|
|
].filter(Boolean);
|
|
|
|
return searchIn.some(field => field?.includes(searchTerm));
|
|
});
|
|
|
|
// Filter by type if specified
|
|
if (type) {
|
|
switch (type) {
|
|
case 'species':
|
|
results = results.filter(p =>
|
|
p.scientificName?.toLowerCase().includes(searchTerm)
|
|
);
|
|
break;
|
|
case 'owner':
|
|
results = results.filter(p =>
|
|
p.owner.name?.toLowerCase().includes(searchTerm)
|
|
);
|
|
break;
|
|
case 'location':
|
|
results = results.filter(
|
|
p =>
|
|
p.location.city?.toLowerCase().includes(searchTerm) ||
|
|
p.location.country?.toLowerCase().includes(searchTerm)
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Also search plants.net if API key is available
|
|
let plantsNetResults = [];
|
|
if (process.env.PLANTS_NET_API_KEY) {
|
|
const plantsNet = getPlantsNetService();
|
|
plantsNetResults = await plantsNet.searchPlant(q);
|
|
}
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
count: results.length,
|
|
results: results,
|
|
plantsNetResults: plantsNetResults,
|
|
});
|
|
} catch (error: any) {
|
|
console.error('Error searching plants:', error);
|
|
res.status(500).json({ error: error.message || 'Internal server error' });
|
|
}
|
|
}
|