Merge: Grower Advisory Agent with tests and type fixes - resolved conflicts
This commit is contained in:
commit
0fcecca424
17 changed files with 1154 additions and 197 deletions
215
__tests__/lib/agents/GrowerAdvisoryAgent.test.ts
Normal file
215
__tests__/lib/agents/GrowerAdvisoryAgent.test.ts
Normal 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 },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ export default function EnvironmentalForm({
|
|||
onChange({
|
||||
...value,
|
||||
[section]: {
|
||||
...currentSection,
|
||||
...(currentSection as object || {}),
|
||||
...updates,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
*/
|
||||
async runOnce(): Promise<AgentTask | null> {
|
||||
const blockchain = getBlockchain();
|
||||
const chain = blockchain.getChain();
|
||||
const chain = blockchain.chain;
|
||||
const plants = chain.slice(1); // Skip genesis
|
||||
|
||||
let profilesUpdated = 0;
|
||||
|
|
@ -265,9 +265,9 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
for (const block of healthyPlants) {
|
||||
const env = block.plant.environment;
|
||||
if (env?.soil?.pH) pHValues.push(env.soil.pH);
|
||||
if (env?.climate?.avgTemperature) tempValues.push(env.climate.avgTemperature);
|
||||
if (env?.climate?.avgHumidity) humidityValues.push(env.climate.avgHumidity);
|
||||
if (env?.lighting?.hoursPerDay) lightValues.push(env.lighting.hoursPerDay);
|
||||
if (env?.climate?.temperatureDay) tempValues.push(env.climate.temperatureDay);
|
||||
if (env?.climate?.humidityAverage) humidityValues.push(env.climate.humidityAverage);
|
||||
if (env?.lighting?.naturalLight?.hoursPerDay) lightValues.push(env.lighting.naturalLight.hoursPerDay);
|
||||
}
|
||||
|
||||
const profile: EnvironmentProfile = existing || {
|
||||
|
|
@ -357,15 +357,16 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
|
||||
// Lighting analysis
|
||||
if (env.lighting) {
|
||||
const lightDiff = env.lighting.hoursPerDay
|
||||
? Math.abs(env.lighting.hoursPerDay - profile.optimalConditions.lightHours.optimal)
|
||||
const lightHours = env.lighting.naturalLight?.hoursPerDay || env.lighting.artificialLight?.hoursPerDay;
|
||||
const lightDiff = lightHours
|
||||
? Math.abs(lightHours - profile.optimalConditions.lightHours.optimal)
|
||||
: 2;
|
||||
lightingScore = Math.max(0, 100 - lightDiff * 15);
|
||||
|
||||
if (lightDiff > 2) {
|
||||
improvements.push({
|
||||
category: 'lighting',
|
||||
currentState: `${env.lighting.hoursPerDay || 'unknown'} hours/day`,
|
||||
currentState: `${lightHours || 'unknown'} hours/day`,
|
||||
recommendedState: `${profile.optimalConditions.lightHours.optimal} hours/day`,
|
||||
priority: lightDiff > 4 ? 'high' : 'medium',
|
||||
expectedImpact: 'Better photosynthesis and growth',
|
||||
|
|
@ -376,11 +377,11 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
|
||||
// Climate analysis
|
||||
if (env.climate) {
|
||||
const tempDiff = env.climate.avgTemperature
|
||||
? Math.abs(env.climate.avgTemperature - profile.optimalConditions.temperature.optimal)
|
||||
const tempDiff = env.climate.temperatureDay
|
||||
? Math.abs(env.climate.temperatureDay - profile.optimalConditions.temperature.optimal)
|
||||
: 5;
|
||||
const humDiff = env.climate.avgHumidity
|
||||
? Math.abs(env.climate.avgHumidity - profile.optimalConditions.humidity.optimal)
|
||||
const humDiff = env.climate.humidityAverage
|
||||
? Math.abs(env.climate.humidityAverage - profile.optimalConditions.humidity.optimal)
|
||||
: 10;
|
||||
|
||||
climateScore = Math.max(0, 100 - tempDiff * 5 - humDiff * 1);
|
||||
|
|
@ -388,7 +389,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
if (tempDiff > 3) {
|
||||
improvements.push({
|
||||
category: 'climate',
|
||||
currentState: `${env.climate.avgTemperature?.toFixed(1) || 'unknown'}°C`,
|
||||
currentState: `${env.climate.temperatureDay?.toFixed(1) || 'unknown'}°C`,
|
||||
recommendedState: `${profile.optimalConditions.temperature.optimal}°C`,
|
||||
priority: tempDiff > 6 ? 'high' : 'medium',
|
||||
expectedImpact: 'Reduced stress and improved growth',
|
||||
|
|
@ -408,7 +409,8 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
// Nutrients analysis
|
||||
if (env.nutrients) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -462,7 +464,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
|
||||
// Find common soil types
|
||||
const soilTypes = plantsWithEnv
|
||||
.map(p => p.plant.environment?.soil?.soilType)
|
||||
.map(p => p.plant.environment?.soil?.type)
|
||||
.filter(Boolean);
|
||||
|
||||
const commonSoilType = this.findMostCommon(soilTypes as string[]);
|
||||
|
|
@ -471,7 +473,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
patterns.push({
|
||||
patternId: `pattern-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
|
||||
species,
|
||||
conditions: { soil: { soilType: commonSoilType } } as any,
|
||||
conditions: { soil: { type: commonSoilType } } as any,
|
||||
successMetric: 'health',
|
||||
successValue: 85,
|
||||
sampleSize: plantsWithEnv.length,
|
||||
|
|
@ -527,7 +529,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
if (cached) return cached;
|
||||
|
||||
const blockchain = getBlockchain();
|
||||
const chain = blockchain.getChain();
|
||||
const chain = blockchain.chain;
|
||||
|
||||
const block1 = chain.find(b => b.plant.id === plant1Id);
|
||||
const block2 = chain.find(b => b.plant.id === plant2Id);
|
||||
|
|
@ -545,14 +547,14 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
// Compare soil
|
||||
if (env1?.soil && env2?.soil) {
|
||||
totalFactors++;
|
||||
if (env1.soil.soilType === env2.soil.soilType) {
|
||||
if (env1.soil.type === env2.soil.type) {
|
||||
matchingFactors.push('Soil type');
|
||||
matchScore++;
|
||||
} else {
|
||||
differingFactors.push({
|
||||
factor: 'Soil type',
|
||||
plant1Value: env1.soil.soilType,
|
||||
plant2Value: env2.soil.soilType
|
||||
plant1Value: env1.soil.type,
|
||||
plant2Value: env2.soil.type
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +590,7 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
if (env1?.climate && env2?.climate) {
|
||||
totalFactors++;
|
||||
const tempDiff = Math.abs(
|
||||
(env1.climate.avgTemperature || 0) - (env2.climate.avgTemperature || 0)
|
||||
(env1.climate.temperatureDay || 0) - (env2.climate.temperatureDay || 0)
|
||||
);
|
||||
if (tempDiff < 3) {
|
||||
matchingFactors.push('Temperature');
|
||||
|
|
@ -596,8 +598,8 @@ export class EnvironmentAnalysisAgent extends BaseAgent {
|
|||
} else {
|
||||
differingFactors.push({
|
||||
factor: 'Temperature',
|
||||
plant1Value: env1.climate.avgTemperature,
|
||||
plant2Value: env2.climate.avgTemperature
|
||||
plant1Value: env1.climate.temperatureDay,
|
||||
plant2Value: env2.climate.temperatureDay
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export class GrowerAdvisoryAgent extends BaseAgent {
|
|||
*/
|
||||
private updateGrowerProfiles(): void {
|
||||
const blockchain = getBlockchain();
|
||||
const chain = blockchain.getChain().slice(1);
|
||||
const chain = blockchain.chain.slice(1);
|
||||
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export class NetworkDiscoveryAgent extends BaseAgent {
|
|||
*/
|
||||
async runOnce(): Promise<AgentTask | null> {
|
||||
const blockchain = getBlockchain();
|
||||
const chain = blockchain.getChain();
|
||||
const chain = blockchain.chain;
|
||||
const plants = chain.slice(1);
|
||||
|
||||
// Build network from plant data
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export class PlantLineageAgent extends BaseAgent {
|
|||
*/
|
||||
async runOnce(): Promise<AgentTask | null> {
|
||||
const blockchain = getBlockchain();
|
||||
const chain = blockchain.getChain();
|
||||
const chain = blockchain.chain;
|
||||
|
||||
// Skip genesis block
|
||||
const plantBlocks = chain.slice(1);
|
||||
|
|
@ -133,7 +133,7 @@ export class PlantLineageAgent extends BaseAgent {
|
|||
totalLineageSize: ancestors.length + descendants.length + 1,
|
||||
propagationChain,
|
||||
geographicSpread,
|
||||
oldestAncestorDate: oldestAncestor?.timestamp || plant.dateAcquired,
|
||||
oldestAncestorDate: oldestAncestor?.timestamp || plant.registeredAt,
|
||||
healthScore: this.calculateHealthScore(plant, chain)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ export class SustainabilityAgent extends BaseAgent {
|
|||
*/
|
||||
private calculateWaterMetrics(): WaterMetrics {
|
||||
const blockchain = getBlockchain();
|
||||
const plantCount = blockchain.getChain().length - 1;
|
||||
const plantCount = blockchain.chain.length - 1;
|
||||
|
||||
// Simulate water usage based on plant count
|
||||
// Vertical farms use ~10% of traditional water
|
||||
|
|
@ -265,7 +265,7 @@ export class SustainabilityAgent extends BaseAgent {
|
|||
*/
|
||||
private calculateWasteMetrics(): WasteMetrics {
|
||||
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 totalPlants = plants.length;
|
||||
|
|
@ -311,7 +311,7 @@ export class SustainabilityAgent extends BaseAgent {
|
|||
|
||||
// Biodiversity: based on plant variety
|
||||
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 biodiversity = Math.min(100, 30 + uniqueSpecies * 5);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ export class PlantChain {
|
|||
private plantIndex: Map<string, PlantBlock>; // Quick lookup by plant ID
|
||||
|
||||
constructor(difficulty: number = 4) {
|
||||
this.chain = [this.createGenesisBlock()];
|
||||
this.difficulty = difficulty;
|
||||
this.plantIndex = new Map();
|
||||
this.chain = [this.createGenesisBlock()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,6 +2,15 @@
|
|||
|
||||
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 {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export interface PlantingRecommendation {
|
|||
|
||||
// Quantities
|
||||
recommendedQuantity: number;
|
||||
quantityUnit: 'plants' | 'seeds' | 'kg_expected_yield';
|
||||
quantityUnit: 'plants' | 'seeds' | 'kg_expected_yield' | 'sqm';
|
||||
expectedYieldKg: number;
|
||||
yieldConfidence: number; // 0-100
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export interface FuzzyLocation {
|
|||
*/
|
||||
export function generateAnonymousId(): string {
|
||||
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 {
|
||||
const algorithm = 'aes-256-cbc';
|
||||
const keyHash = crypto.createHash('sha256').update(key).digest();
|
||||
const iv = crypto.randomBytes(16);
|
||||
const keyHash = new Uint8Array(crypto.createHash('sha256').update(key).digest());
|
||||
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');
|
||||
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 {
|
||||
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 iv = Buffer.from(parts[0], 'hex');
|
||||
const iv = new Uint8Array(Buffer.from(parts[0], 'hex'));
|
||||
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');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ module.exports = withPWA({
|
|||
defaultLocale: "en",
|
||||
},
|
||||
images: {
|
||||
domains: process.env.NEXT_IMAGE_DOMAIN ? [process.env.NEXT_IMAGE_DOMAIN] : [],
|
||||
domains: [process.env.NEXT_IMAGE_DOMAIN].filter(Boolean),
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
|
|
|
|||
1030
package-lock.json
generated
1030
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -85,8 +85,8 @@
|
|||
"prisma": "^5.7.0",
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"tailwindcss": "^3.0.15",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^4.5.5"
|
||||
"ts-jest": "^29.4.5",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": [
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ export default function ZoneManagement() {
|
|||
Start New Batch
|
||||
</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">
|
||||
Harvest
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"downlevelIteration": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
|
|
|
|||
Loading…
Reference in a new issue