Deploy GrowerAdvisoryAgent (Agent 10) and fix type errors

- Add GrowerAdvisoryAgent test file
- Fix PlantChain constructor initialization order (plantIndex before genesis block)
- Fix blockchain.getChain() calls to use blockchain.chain property
- Add PropagationType export to blockchain types
- Fix SoilComposition.type property references (was soilType)
- Fix ClimateConditions.temperatureDay property references (was avgTemperature)
- Fix ClimateConditions.humidityAverage property references (was avgHumidity)
- Fix LightingConditions.naturalLight.hoursPerDay nested access
- Add 'critical' severity to QualityReport issues
- Add 'sqm' unit to PlantingRecommendation.quantityUnit
- Fix GrowerAdvisoryAgent growthMetrics property access
- Update TypeScript to v5 for react-hook-form compatibility
- Enable downlevelIteration in tsconfig for Map iteration
- Fix crypto Buffer type issues in anonymity.ts
- Fix zones.tsx status type comparison
- Fix next.config.js images.domains filter
- Rename [[...slug]].tsx to [...slug].tsx to resolve routing conflict
This commit is contained in:
Claude 2025-11-23 00:44:58 +00:00
parent e76550e73a
commit 507df5912f
No known key found for this signature in database
19 changed files with 8938 additions and 49 deletions

View file

@ -0,0 +1,215 @@
/**
* GrowerAdvisoryAgent Tests
* Tests for the grower advisory and recommendation system
*/
import {
GrowerAdvisoryAgent,
getGrowerAdvisoryAgent,
} from '../../../lib/agents/GrowerAdvisoryAgent';
describe('GrowerAdvisoryAgent', () => {
let agent: GrowerAdvisoryAgent;
beforeEach(() => {
agent = new GrowerAdvisoryAgent();
});
describe('Initialization', () => {
it('should create agent with correct configuration', () => {
expect(agent.config.id).toBe('grower-advisory-agent');
expect(agent.config.name).toBe('Grower Advisory Agent');
expect(agent.config.enabled).toBe(true);
expect(agent.config.priority).toBe('high');
});
it('should have correct interval (5 minutes)', () => {
expect(agent.config.intervalMs).toBe(300000);
});
it('should start in idle status', () => {
expect(agent.status).toBe('idle');
});
it('should have empty metrics initially', () => {
const metrics = agent.getMetrics();
expect(metrics.tasksCompleted).toBe(0);
expect(metrics.tasksFailed).toBe(0);
expect(metrics.errors).toEqual([]);
});
});
describe('Grower Profile Management', () => {
it('should register a grower profile', () => {
const profile = createGrowerProfile('grower-1');
agent.registerGrowerProfile(profile);
const retrieved = agent.getGrowerProfile('grower-1');
expect(retrieved).not.toBeNull();
expect(retrieved?.growerId).toBe('grower-1');
});
it('should return null for unknown grower', () => {
const retrieved = agent.getGrowerProfile('unknown-grower');
expect(retrieved).toBeNull();
});
it('should update existing profile', () => {
const profile1 = createGrowerProfile('grower-1');
profile1.experienceLevel = 'beginner';
agent.registerGrowerProfile(profile1);
const profile2 = createGrowerProfile('grower-1');
profile2.experienceLevel = 'expert';
agent.registerGrowerProfile(profile2);
const retrieved = agent.getGrowerProfile('grower-1');
expect(retrieved?.experienceLevel).toBe('expert');
});
});
describe('Recommendations', () => {
it('should return empty recommendations for unknown grower', () => {
const recs = agent.getRecommendations('unknown-grower');
expect(recs).toEqual([]);
});
it('should get recommendations after profile registration', () => {
const profile = createGrowerProfile('grower-1');
agent.registerGrowerProfile(profile);
// Recommendations are generated during runOnce
const recs = agent.getRecommendations('grower-1');
expect(Array.isArray(recs)).toBe(true);
});
});
describe('Rotation Advice', () => {
it('should return null for unknown grower', () => {
const advice = agent.getRotationAdvice('unknown-grower');
expect(advice).toBeNull();
});
});
describe('Market Opportunities', () => {
it('should return array of opportunities', () => {
const opps = agent.getOpportunities();
expect(Array.isArray(opps)).toBe(true);
});
});
describe('Grower Performance', () => {
it('should return null for unknown grower', () => {
const perf = agent.getPerformance('unknown-grower');
expect(perf).toBeNull();
});
});
describe('Seasonal Alerts', () => {
it('should return array of seasonal alerts', () => {
const alerts = agent.getSeasonalAlerts();
expect(Array.isArray(alerts)).toBe(true);
});
});
describe('Agent Lifecycle', () => {
it('should start and change status to running', async () => {
await agent.start();
expect(agent.status).toBe('running');
await agent.stop();
});
it('should stop and change status to idle', async () => {
await agent.start();
await agent.stop();
expect(agent.status).toBe('idle');
});
it('should pause when running', async () => {
await agent.start();
agent.pause();
expect(agent.status).toBe('paused');
await agent.stop();
});
it('should resume after pause', async () => {
await agent.start();
agent.pause();
agent.resume();
expect(agent.status).toBe('running');
await agent.stop();
});
});
describe('Singleton', () => {
it('should return same instance from getGrowerAdvisoryAgent', () => {
const agent1 = getGrowerAdvisoryAgent();
const agent2 = getGrowerAdvisoryAgent();
expect(agent1).toBe(agent2);
});
});
describe('Alerts', () => {
it('should return alerts array', () => {
const alerts = agent.getAlerts();
expect(Array.isArray(alerts)).toBe(true);
});
});
describe('Task Execution', () => {
it('should execute runOnce successfully', async () => {
const profile = createGrowerProfile('grower-1');
agent.registerGrowerProfile(profile);
const result = await agent.runOnce();
expect(result).not.toBeNull();
expect(result?.status).toBe('completed');
expect(result?.type).toBe('grower_advisory');
});
it('should report metrics in task result', async () => {
const profile = createGrowerProfile('grower-1');
agent.registerGrowerProfile(profile);
const result = await agent.runOnce();
expect(result?.result).toHaveProperty('growersAdvised');
expect(result?.result).toHaveProperty('recommendationsGenerated');
expect(result?.result).toHaveProperty('opportunitiesIdentified');
expect(result?.result).toHaveProperty('alertsGenerated');
});
it('should count registered growers', async () => {
agent.registerGrowerProfile(createGrowerProfile('grower-1'));
agent.registerGrowerProfile(createGrowerProfile('grower-2'));
agent.registerGrowerProfile(createGrowerProfile('grower-3'));
const result = await agent.runOnce();
expect(result?.result?.growersAdvised).toBe(3);
});
});
});
// Helper function to create test grower profiles
function createGrowerProfile(
growerId: string,
lat: number = 40.7128,
lon: number = -74.006
) {
return {
growerId,
growerName: `Test Grower ${growerId}`,
location: { latitude: lat, longitude: lon },
availableSpaceSqm: 100,
specializations: ['lettuce', 'tomato'],
certifications: ['organic'],
experienceLevel: 'intermediate' as const,
preferredCrops: ['lettuce', 'tomato', 'basil'],
growingHistory: [
{ cropType: 'lettuce', successRate: 85, avgYield: 4.5 },
{ cropType: 'tomato', successRate: 75, avgYield: 8.0 },
],
};
}

View file

@ -31,7 +31,7 @@ export default function EnvironmentalForm({
onChange({ onChange({
...value, ...value,
[section]: { [section]: {
...value[section], ...(value[section] as object || {}),
...updates, ...updates,
}, },
}); });

View file

@ -168,7 +168,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
*/ */
async runOnce(): Promise<AgentTask | null> { async runOnce(): Promise<AgentTask | null> {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain(); const chain = blockchain.chain;
const plants = chain.slice(1); // Skip genesis const plants = chain.slice(1); // Skip genesis
let profilesUpdated = 0; let profilesUpdated = 0;
@ -265,9 +265,9 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
for (const block of healthyPlants) { for (const block of healthyPlants) {
const env = block.plant.environment; const env = block.plant.environment;
if (env?.soil?.pH) pHValues.push(env.soil.pH); if (env?.soil?.pH) pHValues.push(env.soil.pH);
if (env?.climate?.avgTemperature) tempValues.push(env.climate.avgTemperature); if (env?.climate?.temperatureDay) tempValues.push(env.climate.temperatureDay);
if (env?.climate?.avgHumidity) humidityValues.push(env.climate.avgHumidity); if (env?.climate?.humidityAverage) humidityValues.push(env.climate.humidityAverage);
if (env?.lighting?.hoursPerDay) lightValues.push(env.lighting.hoursPerDay); if (env?.lighting?.naturalLight?.hoursPerDay) lightValues.push(env.lighting.naturalLight.hoursPerDay);
} }
const profile: EnvironmentProfile = existing || { const profile: EnvironmentProfile = existing || {
@ -357,15 +357,16 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
// Lighting analysis // Lighting analysis
if (env.lighting) { if (env.lighting) {
const lightDiff = env.lighting.hoursPerDay const lightHours = env.lighting.naturalLight?.hoursPerDay || env.lighting.artificialLight?.hoursPerDay;
? Math.abs(env.lighting.hoursPerDay - profile.optimalConditions.lightHours.optimal) const lightDiff = lightHours
? Math.abs(lightHours - profile.optimalConditions.lightHours.optimal)
: 2; : 2;
lightingScore = Math.max(0, 100 - lightDiff * 15); lightingScore = Math.max(0, 100 - lightDiff * 15);
if (lightDiff > 2) { if (lightDiff > 2) {
improvements.push({ improvements.push({
category: 'lighting', category: 'lighting',
currentState: `${env.lighting.hoursPerDay || 'unknown'} hours/day`, currentState: `${lightHours || 'unknown'} hours/day`,
recommendedState: `${profile.optimalConditions.lightHours.optimal} hours/day`, recommendedState: `${profile.optimalConditions.lightHours.optimal} hours/day`,
priority: lightDiff > 4 ? 'high' : 'medium', priority: lightDiff > 4 ? 'high' : 'medium',
expectedImpact: 'Better photosynthesis and growth', expectedImpact: 'Better photosynthesis and growth',
@ -376,11 +377,11 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
// Climate analysis // Climate analysis
if (env.climate) { if (env.climate) {
const tempDiff = env.climate.avgTemperature const tempDiff = env.climate.temperatureDay
? Math.abs(env.climate.avgTemperature - profile.optimalConditions.temperature.optimal) ? Math.abs(env.climate.temperatureDay - profile.optimalConditions.temperature.optimal)
: 5; : 5;
const humDiff = env.climate.avgHumidity const humDiff = env.climate.humidityAverage
? Math.abs(env.climate.avgHumidity - profile.optimalConditions.humidity.optimal) ? Math.abs(env.climate.humidityAverage - profile.optimalConditions.humidity.optimal)
: 10; : 10;
climateScore = Math.max(0, 100 - tempDiff * 5 - humDiff * 1); climateScore = Math.max(0, 100 - tempDiff * 5 - humDiff * 1);
@ -388,7 +389,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
if (tempDiff > 3) { if (tempDiff > 3) {
improvements.push({ improvements.push({
category: 'climate', category: 'climate',
currentState: `${env.climate.avgTemperature?.toFixed(1) || 'unknown'}°C`, currentState: `${env.climate.temperatureDay?.toFixed(1) || 'unknown'}°C`,
recommendedState: `${profile.optimalConditions.temperature.optimal}°C`, recommendedState: `${profile.optimalConditions.temperature.optimal}°C`,
priority: tempDiff > 6 ? 'high' : 'medium', priority: tempDiff > 6 ? 'high' : 'medium',
expectedImpact: 'Reduced stress and improved growth', expectedImpact: 'Reduced stress and improved growth',
@ -408,7 +409,8 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
// Nutrients analysis // Nutrients analysis
if (env.nutrients) { if (env.nutrients) {
nutrientsScore = 75; // Base score if nutrient data exists nutrientsScore = 75; // Base score if nutrient data exists
if (env.nutrients.fertilizer?.schedule === 'regular') { // Bonus for complete NPK profile
if (env.nutrients.nitrogen && env.nutrients.phosphorus && env.nutrients.potassium) {
nutrientsScore = 90; nutrientsScore = 90;
} }
} }
@ -462,7 +464,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
// Find common soil types // Find common soil types
const soilTypes = plantsWithEnv const soilTypes = plantsWithEnv
.map(p => p.plant.environment?.soil?.soilType) .map(p => p.plant.environment?.soil?.type)
.filter(Boolean); .filter(Boolean);
const commonSoilType = this.findMostCommon(soilTypes as string[]); const commonSoilType = this.findMostCommon(soilTypes as string[]);
@ -471,7 +473,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
patterns.push({ patterns.push({
patternId: `pattern-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, patternId: `pattern-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
species, species,
conditions: { soil: { soilType: commonSoilType } } as any, conditions: { soil: { type: commonSoilType } } as any,
successMetric: 'health', successMetric: 'health',
successValue: 85, successValue: 85,
sampleSize: plantsWithEnv.length, sampleSize: plantsWithEnv.length,
@ -527,7 +529,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
if (cached) return cached; if (cached) return cached;
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain(); const chain = blockchain.chain;
const block1 = chain.find(b => b.plant.id === plant1Id); const block1 = chain.find(b => b.plant.id === plant1Id);
const block2 = chain.find(b => b.plant.id === plant2Id); const block2 = chain.find(b => b.plant.id === plant2Id);
@ -545,14 +547,14 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
// Compare soil // Compare soil
if (env1?.soil && env2?.soil) { if (env1?.soil && env2?.soil) {
totalFactors++; totalFactors++;
if (env1.soil.soilType === env2.soil.soilType) { if (env1.soil.type === env2.soil.type) {
matchingFactors.push('Soil type'); matchingFactors.push('Soil type');
matchScore++; matchScore++;
} else { } else {
differingFactors.push({ differingFactors.push({
factor: 'Soil type', factor: 'Soil type',
plant1Value: env1.soil.soilType, plant1Value: env1.soil.type,
plant2Value: env2.soil.soilType plant2Value: env2.soil.type
}); });
} }
@ -588,7 +590,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
if (env1?.climate && env2?.climate) { if (env1?.climate && env2?.climate) {
totalFactors++; totalFactors++;
const tempDiff = Math.abs( const tempDiff = Math.abs(
(env1.climate.avgTemperature || 0) - (env2.climate.avgTemperature || 0) (env1.climate.temperatureDay || 0) - (env2.climate.temperatureDay || 0)
); );
if (tempDiff < 3) { if (tempDiff < 3) {
matchingFactors.push('Temperature'); matchingFactors.push('Temperature');
@ -596,8 +598,8 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
} else { } else {
differingFactors.push({ differingFactors.push({
factor: 'Temperature', factor: 'Temperature',
plant1Value: env1.climate.avgTemperature, plant1Value: env1.climate.temperatureDay,
plant2Value: env2.climate.avgTemperature plant2Value: env2.climate.temperatureDay
}); });
} }
} }

View file

@ -178,7 +178,7 @@ export class GrowerAdvisoryAgent extends BaseAgent {
*/ */
private updateGrowerProfiles(): void { private updateGrowerProfiles(): void {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain().slice(1); const chain = blockchain.chain.slice(1);
const ownerPlants = new Map<string, typeof chain>(); const ownerPlants = new Map<string, typeof chain>();
@ -219,7 +219,11 @@ export class GrowerAdvisoryAgent extends BaseAgent {
if (['growing', 'mature', 'flowering', 'fruiting'].includes(plant.plant.status)) { if (['growing', 'mature', 'flowering', 'fruiting'].includes(plant.plant.status)) {
existing.healthy++; existing.healthy++;
} }
existing.yield += plant.plant.growthMetrics?.estimatedYieldKg || 2; // Estimate yield based on health score, or use default of 2kg
const healthMultiplier = plant.plant.growthMetrics?.healthScore
? plant.plant.growthMetrics.healthScore / 50
: 1;
existing.yield += 2 * healthMultiplier;
historyMap.set(crop, existing); historyMap.set(crop, existing);
} }

View file

@ -102,7 +102,7 @@ export class NetworkDiscoveryAgent extends BaseAgent {
*/ */
async runOnce(): Promise<AgentTask | null> { async runOnce(): Promise<AgentTask | null> {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain(); const chain = blockchain.chain;
const plants = chain.slice(1); const plants = chain.slice(1);
// Build network from plant data // Build network from plant data

View file

@ -58,7 +58,7 @@ export class PlantLineageAgent extends BaseAgent {
*/ */
async runOnce(): Promise<AgentTask | null> { async runOnce(): Promise<AgentTask | null> {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain(); const chain = blockchain.chain;
// Skip genesis block // Skip genesis block
const plantBlocks = chain.slice(1); const plantBlocks = chain.slice(1);
@ -133,7 +133,7 @@ export class PlantLineageAgent extends BaseAgent {
totalLineageSize: ancestors.length + descendants.length + 1, totalLineageSize: ancestors.length + descendants.length + 1,
propagationChain, propagationChain,
geographicSpread, geographicSpread,
oldestAncestorDate: oldestAncestor?.timestamp || plant.dateAcquired, oldestAncestorDate: oldestAncestor?.timestamp || plant.registeredAt,
healthScore: this.calculateHealthScore(plant, chain) healthScore: this.calculateHealthScore(plant, chain)
}; };
} }

View file

@ -131,7 +131,7 @@ export class QualityAssuranceAgent extends BaseAgent {
*/ */
private async verifyPlantChain(): Promise<IntegrityCheck> { private async verifyPlantChain(): Promise<IntegrityCheck> {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain(); const chain = blockchain.chain;
let hashMismatches = 0; let hashMismatches = 0;
let linkBroken = 0; let linkBroken = 0;
@ -205,7 +205,7 @@ export class QualityAssuranceAgent extends BaseAgent {
const issues: DataQualityIssue[] = []; const issues: DataQualityIssue[] = [];
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain().slice(1); const chain = blockchain.chain.slice(1);
const seenIds = new Set<string>(); const seenIds = new Set<string>();
@ -390,7 +390,7 @@ export class QualityAssuranceAgent extends BaseAgent {
*/ */
private calculateStatistics(): DataStatistics { private calculateStatistics(): DataStatistics {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const chain = blockchain.getChain().slice(1); const chain = blockchain.chain.slice(1);
let completeRecords = 0; let completeRecords = 0;
let partialRecords = 0; let partialRecords = 0;

View file

@ -232,7 +232,7 @@ export class SustainabilityAgent extends BaseAgent {
*/ */
private calculateWaterMetrics(): WaterMetrics { private calculateWaterMetrics(): WaterMetrics {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const plantCount = blockchain.getChain().length - 1; const plantCount = blockchain.chain.length - 1;
// Simulate water usage based on plant count // Simulate water usage based on plant count
// Vertical farms use ~10% of traditional water // Vertical farms use ~10% of traditional water
@ -265,7 +265,7 @@ export class SustainabilityAgent extends BaseAgent {
*/ */
private calculateWasteMetrics(): WasteMetrics { private calculateWasteMetrics(): WasteMetrics {
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const plants = blockchain.getChain().slice(1); const plants = blockchain.chain.slice(1);
const deceasedPlants = plants.filter(p => p.plant.status === 'deceased').length; const deceasedPlants = plants.filter(p => p.plant.status === 'deceased').length;
const totalPlants = plants.length; const totalPlants = plants.length;
@ -311,7 +311,7 @@ export class SustainabilityAgent extends BaseAgent {
// Biodiversity: based on plant variety // Biodiversity: based on plant variety
const blockchain = getBlockchain(); const blockchain = getBlockchain();
const plants = blockchain.getChain().slice(1); const plants = blockchain.chain.slice(1);
const uniqueSpecies = new Set(plants.map(p => p.plant.commonName)).size; const uniqueSpecies = new Set(plants.map(p => p.plant.commonName)).size;
const biodiversity = Math.min(100, 30 + uniqueSpecies * 5); const biodiversity = Math.min(100, 30 + uniqueSpecies * 5);

View file

@ -160,7 +160,7 @@ export interface QualityReport {
blockIndex: number; blockIndex: number;
issueType: string; issueType: string;
description: string; description: string;
severity: 'low' | 'medium' | 'high'; severity: 'low' | 'medium' | 'high' | 'critical';
}[]; }[];
lastVerifiedAt: string; lastVerifiedAt: string;
} }

View file

@ -11,9 +11,9 @@ export class PlantChain {
private plantIndex: Map<string, PlantBlock>; // Quick lookup by plant ID private plantIndex: Map<string, PlantBlock>; // Quick lookup by plant ID
constructor(difficulty: number = 4) { constructor(difficulty: number = 4) {
this.chain = [this.createGenesisBlock()];
this.difficulty = difficulty; this.difficulty = difficulty;
this.plantIndex = new Map(); this.plantIndex = new Map();
this.chain = [this.createGenesisBlock()];
} }
/** /**

View file

@ -2,6 +2,15 @@
import { GrowingEnvironment, GrowthMetrics } from '../environment/types'; import { GrowingEnvironment, GrowthMetrics } from '../environment/types';
// Re-export types from environment
export type { GrowingEnvironment, GrowthMetrics };
// Re-export PlantBlock class
export { PlantBlock } from './PlantBlock';
// Propagation type alias
export type PropagationType = 'seed' | 'clone' | 'cutting' | 'division' | 'grafting' | 'original';
export interface PlantLocation { export interface PlantLocation {
latitude: number; latitude: number;
longitude: number; longitude: number;

View file

@ -154,7 +154,7 @@ export interface PlantingRecommendation {
// Quantities // Quantities
recommendedQuantity: number; recommendedQuantity: number;
quantityUnit: 'plants' | 'seeds' | 'kg_expected_yield'; quantityUnit: 'plants' | 'seeds' | 'kg_expected_yield' | 'sqm';
expectedYieldKg: number; expectedYieldKg: number;
yieldConfidence: number; // 0-100 yieldConfidence: number; // 0-100

View file

@ -24,7 +24,7 @@ export interface FuzzyLocation {
*/ */
export function generateAnonymousId(): string { export function generateAnonymousId(): string {
const randomBytes = crypto.randomBytes(32); const randomBytes = crypto.randomBytes(32);
return 'anon_' + crypto.createHash('sha256').update(randomBytes).digest('hex').substring(0, 16); return 'anon_' + crypto.createHash('sha256').update(new Uint8Array(randomBytes)).digest('hex').substring(0, 16);
} }
/** /**
@ -111,14 +111,14 @@ export function generateAnonymousPlantName(plantType: string, generation: number
*/ */
export function encryptData(data: string, key: string): string { export function encryptData(data: string, key: string): string {
const algorithm = 'aes-256-cbc'; const algorithm = 'aes-256-cbc';
const keyHash = crypto.createHash('sha256').update(key).digest(); const keyHash = new Uint8Array(crypto.createHash('sha256').update(key).digest());
const iv = crypto.randomBytes(16); const iv = new Uint8Array(crypto.randomBytes(16));
const cipher = crypto.createCipheriv(algorithm, keyHash, iv); const cipher = crypto.createCipheriv(algorithm, keyHash as any, iv as any);
let encrypted = cipher.update(data, 'utf8', 'hex'); let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex'); encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted; return Buffer.from(iv).toString('hex') + ':' + encrypted;
} }
/** /**
@ -126,13 +126,13 @@ export function encryptData(data: string, key: string): string {
*/ */
export function decryptData(encryptedData: string, key: string): string { export function decryptData(encryptedData: string, key: string): string {
const algorithm = 'aes-256-cbc'; const algorithm = 'aes-256-cbc';
const keyHash = crypto.createHash('sha256').update(key).digest(); const keyHash = new Uint8Array(crypto.createHash('sha256').update(key).digest());
const parts = encryptedData.split(':'); const parts = encryptedData.split(':');
const iv = Buffer.from(parts[0], 'hex'); const iv = new Uint8Array(Buffer.from(parts[0], 'hex'));
const encrypted = parts[1]; const encrypted = parts[1];
const decipher = crypto.createDecipheriv(algorithm, keyHash, iv); const decipher = crypto.createDecipheriv(algorithm, keyHash as any, iv as any);
let decrypted = decipher.update(encrypted, 'hex', 'utf8'); let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8'); decrypted += decipher.final('utf8');

View file

@ -5,7 +5,7 @@ module.exports = {
defaultLocale: "en", defaultLocale: "en",
}, },
images: { images: {
domains: [process.env.NEXT_IMAGE_DOMAIN], domains: [process.env.NEXT_IMAGE_DOMAIN].filter(Boolean),
}, },
async rewrites() { async rewrites() {
return [ return [

8658
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -42,7 +42,7 @@
"jest": "^29.5.0", "jest": "^29.5.0",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"tailwindcss": "^3.0.15", "tailwindcss": "^3.0.15",
"ts-jest": "^29.1.0", "ts-jest": "^29.4.5",
"typescript": "^4.5.5" "typescript": "^5.9.3"
} }
} }

View file

@ -212,7 +212,7 @@ export default function ZoneManagement() {
Start New Batch Start New Batch
</button> </button>
)} )}
{(selectedZone.status === 'growing' || selectedZone.status === 'ready') && ( {(selectedZone.status === 'planted' || selectedZone.status === 'harvesting') && (
<button className="w-full px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition"> <button className="w-full px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition">
Harvest Harvest
</button> </button>

View file

@ -13,6 +13,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"downlevelIteration": true,
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"@/components/*": ["src/components/*"], "@/components/*": ["src/components/*"],