1291 lines
43 KiB
JavaScript
1291 lines
43 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const bodyParser = require('body-parser');
|
|
const Database = require('better-sqlite3');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const multer = require('multer');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3001;
|
|
const DB_DIR = path.join(__dirname, 'database');
|
|
const DB_PATH = path.join(DB_DIR, 'gardentrack.db');
|
|
|
|
// Ensure uploads directory exists
|
|
const UPLOADS_DIR = path.join(__dirname, 'uploads');
|
|
if (!fs.existsSync(UPLOADS_DIR)) {
|
|
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
console.log('Created uploads directory');
|
|
}
|
|
|
|
// Ensure database directory exists
|
|
if (!fs.existsSync(DB_DIR)) {
|
|
fs.mkdirSync(DB_DIR, { recursive: true });
|
|
console.log('Created database directory');
|
|
}
|
|
|
|
// Middleware
|
|
app.use(cors());
|
|
app.use(bodyParser.json());
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
|
|
// Serve uploaded files statically
|
|
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
|
|
|
// Configure multer for file uploads
|
|
const storage = multer.diskStorage({
|
|
destination: function (req, file, cb) {
|
|
cb(null, UPLOADS_DIR);
|
|
},
|
|
filename: function (req, file, cb) {
|
|
// Generate unique filename with timestamp
|
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
const extension = path.extname(file.originalname);
|
|
cb(null, 'plant-' + uniqueSuffix + extension);
|
|
}
|
|
});
|
|
|
|
const fileFilter = (req, file, cb) => {
|
|
// Accept only image files
|
|
if (file.mimetype.startsWith('image/')) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error('Only image files are allowed!'), false);
|
|
}
|
|
};
|
|
|
|
const upload = multer({
|
|
storage: storage,
|
|
fileFilter: fileFilter,
|
|
limits: {
|
|
fileSize: 5 * 1024 * 1024 // 5MB limit
|
|
}
|
|
});
|
|
|
|
// Database connection and initialization
|
|
let db;
|
|
try {
|
|
db = new Database(DB_PATH);
|
|
console.log('Connected to SQLite database at:', DB_PATH);
|
|
initializeDatabase();
|
|
} catch (err) {
|
|
console.error('Error opening database:', err.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Initialize database tables
|
|
function initializeDatabase() {
|
|
console.log('Initializing database tables...');
|
|
|
|
try {
|
|
// Plants table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS plants (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
type TEXT NOT NULL,
|
|
variety TEXT NOT NULL,
|
|
purchase_location TEXT NOT NULL,
|
|
seedling_age INTEGER NOT NULL,
|
|
seedling_height REAL NOT NULL,
|
|
planting_date DATE NOT NULL,
|
|
health_status TEXT DEFAULT 'good' CHECK(health_status IN ('good', 'needs-attention', 'dead')),
|
|
current_height REAL,
|
|
photo_url TEXT,
|
|
current_photo_url TEXT,
|
|
estimated_ripening_start INTEGER,
|
|
estimated_ripening_end INTEGER,
|
|
ripening_notes TEXT,
|
|
notes TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
console.log('Plants table ready');
|
|
|
|
// Fertilizers table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS fertilizers (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
brand TEXT,
|
|
type TEXT NOT NULL CHECK(type IN ('organic', 'synthetic', 'liquid', 'granular', 'slow-release')),
|
|
npk_ratio TEXT,
|
|
description TEXT,
|
|
application_rate TEXT,
|
|
frequency TEXT,
|
|
season TEXT,
|
|
notes TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
console.log('Fertilizers table ready');
|
|
|
|
// Chemicals table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS chemicals (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
brand TEXT,
|
|
type TEXT NOT NULL CHECK(type IN ('pesticide', 'herbicide', 'fungicide', 'insecticide', 'miticide')),
|
|
active_ingredient TEXT,
|
|
concentration TEXT,
|
|
target_pests TEXT,
|
|
application_method TEXT,
|
|
safety_period INTEGER,
|
|
notes TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
console.log('Chemicals table ready');
|
|
|
|
// Plant observations table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS plant_observations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
plant_id INTEGER NOT NULL,
|
|
date DATE NOT NULL,
|
|
title TEXT NOT NULL,
|
|
observation TEXT NOT NULL,
|
|
weather_conditions TEXT,
|
|
temperature REAL,
|
|
photo_url TEXT,
|
|
tags TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
console.log('Plant observations table ready');
|
|
|
|
// Harvest processing table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS harvest_processing (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
harvest_id INTEGER NOT NULL,
|
|
process_type TEXT NOT NULL CHECK(process_type IN ('fresh', 'frozen', 'jam', 'dried', 'canned', 'juice', 'sauce', 'pickled', 'other')),
|
|
quantity REAL NOT NULL,
|
|
unit TEXT NOT NULL,
|
|
process_date DATE NOT NULL,
|
|
expiry_date DATE,
|
|
storage_location TEXT,
|
|
notes TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (harvest_id) REFERENCES harvest_records (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
console.log('Harvest processing table ready');
|
|
|
|
// Harvest stock table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS harvest_stock (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
processing_id INTEGER NOT NULL,
|
|
current_quantity REAL NOT NULL,
|
|
unit TEXT NOT NULL,
|
|
last_updated DATE NOT NULL,
|
|
status TEXT DEFAULT 'available' CHECK(status IN ('available', 'consumed', 'expired', 'spoiled')),
|
|
notes TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (processing_id) REFERENCES harvest_processing (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
console.log('Harvest stock table ready');
|
|
|
|
// Plant history table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS plant_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
plant_id INTEGER NOT NULL,
|
|
year INTEGER NOT NULL,
|
|
blooming_date DATE,
|
|
fruiting_date DATE,
|
|
harvest_date DATE,
|
|
current_height REAL,
|
|
current_photo_url TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
console.log('Plant history table ready');
|
|
|
|
// Harvest records table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS harvest_records (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
plant_id INTEGER NOT NULL,
|
|
date DATE NOT NULL,
|
|
quantity REAL NOT NULL,
|
|
unit TEXT NOT NULL,
|
|
weight REAL,
|
|
weight_unit TEXT,
|
|
notes TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
console.log('Harvest records table ready');
|
|
|
|
// Maintenance records table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS maintenance_records (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
plant_id INTEGER NOT NULL,
|
|
date DATE NOT NULL,
|
|
type TEXT NOT NULL CHECK(type IN ('chemical', 'fertilizer', 'watering', 'pruning', 'transplanting', 'other')),
|
|
description TEXT NOT NULL,
|
|
amount TEXT,
|
|
fertilizer_id INTEGER,
|
|
chemical_id INTEGER,
|
|
is_planned BOOLEAN DEFAULT 0,
|
|
is_completed BOOLEAN DEFAULT 1,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (fertilizer_id) REFERENCES fertilizers (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (chemical_id) REFERENCES chemicals (id) ON DELETE SET NULL
|
|
)
|
|
`);
|
|
console.log('Maintenance records table ready');
|
|
|
|
// Tasks table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
plant_id INTEGER,
|
|
type TEXT DEFAULT 'general' CHECK(type IN ('general', 'fertilizer', 'chemical', 'watering', 'pruning', 'transplanting', 'harvesting', 'other')),
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
deadline DATE NOT NULL,
|
|
completed BOOLEAN DEFAULT 0,
|
|
fertilizer_id INTEGER,
|
|
chemical_id INTEGER,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (fertilizer_id) REFERENCES fertilizers (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (chemical_id) REFERENCES chemicals (id) ON DELETE SET NULL
|
|
)
|
|
`);
|
|
console.log('Tasks table ready');
|
|
|
|
// Insert sample data if tables are empty
|
|
const plantCount = db.prepare('SELECT COUNT(*) as count FROM plants').get();
|
|
|
|
if (plantCount.count === 0) {
|
|
console.log('Inserting sample data...');
|
|
insertSampleData();
|
|
} else {
|
|
console.log('Database already contains data');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error initializing database:', err);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Insert sample data
|
|
function insertSampleData() {
|
|
try {
|
|
// Sample fertilizers
|
|
const insertFertilizer = db.prepare(`
|
|
INSERT INTO fertilizers (name, brand, type, npk_ratio, description, application_rate, frequency, season, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertFertilizer.run('All-Purpose Garden Fertilizer', 'Miracle-Gro', 'synthetic', '10-10-10', 'Balanced fertilizer for general garden use', '1 tablespoon per gallon', 'Every 2 weeks', 'Spring-Summer', 'Good for most plants');
|
|
insertFertilizer.run('Organic Compost', 'Local Farm', 'organic', '3-2-2', 'Natural organic matter for soil improvement', '2-3 inches layer', 'Twice yearly', 'Spring-Fall', 'Improves soil structure');
|
|
insertFertilizer.run('Bone Meal', 'Espoma', 'organic', '3-15-0', 'Slow-release phosphorus for root development', '1-2 tablespoons per plant', 'Once per season', 'Spring', 'Great for flowering plants');
|
|
insertFertilizer.run('Liquid Kelp', 'Neptune\'s Harvest', 'liquid', '0-0-1', 'Seaweed extract for plant health', '1 tablespoon per gallon', 'Monthly', 'All seasons', 'Boosts plant immunity');
|
|
|
|
// Sample chemicals
|
|
const insertChemical = db.prepare(`
|
|
INSERT INTO chemicals (name, brand, type, active_ingredient, concentration, target_pests, application_method, safety_period, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertChemical.run('Neem Oil', 'Garden Safe', 'insecticide', 'Azadirachtin', '0.9%', 'Aphids, whiteflies, spider mites', 'Foliar spray', 1, 'Organic option, safe for beneficial insects');
|
|
insertChemical.run('Copper Fungicide', 'Bonide', 'fungicide', 'Copper sulfate', '8%', 'Blight, rust, mildew', 'Foliar spray', 7, 'Use in early morning or evening');
|
|
insertChemical.run('Bt Spray', 'Safer Brand', 'pesticide', 'Bacillus thuringiensis', '0.5%', 'Caterpillars, larvae', 'Foliar spray', 0, 'Organic, targets specific pests');
|
|
insertChemical.run('Systemic Insecticide', 'Bayer', 'insecticide', 'Imidacloprid', '1.47%', 'Aphids, scale, thrips', 'Soil drench', 21, 'Long-lasting protection');
|
|
|
|
// Sample plants
|
|
const insertPlant = db.prepare(`
|
|
INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertPlant.run('tree', 'Apple - Honeycrisp', 'Local Nursery', 12, 45.5, '2022-04-15', 'good', 180.2, 'Growing well, good fruit production');
|
|
insertPlant.run('shrub', 'Blueberry - Bluecrop', 'Garden Center', 8, 25.0, '2023-03-20', 'needs-attention', 65.8, 'Leaves showing slight discoloration');
|
|
insertPlant.run('herb', 'Basil - Sweet Genovese', 'Online Store', 3, 8.2, '2024-05-10', 'good', 22.5, 'Producing well, regular harvests');
|
|
|
|
// Sample tasks
|
|
const insertTask = db.prepare(`
|
|
INSERT INTO tasks (plant_id, title, description, deadline, completed)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertTask.run(1, 'Apply fertilizer', 'Apply spring fertilizer to apple trees', '2024-03-15', 0);
|
|
insertTask.run(2, 'Prune blueberry bushes', 'Annual pruning before spring growth', '2024-02-28', 1);
|
|
insertTask.run(3, 'Harvest basil', 'Regular harvest to encourage growth', '2024-06-01', 0);
|
|
|
|
// Sample maintenance records
|
|
const insertMaintenance = db.prepare(`
|
|
INSERT INTO maintenance_records (plant_id, date, type, description, amount, fertilizer_id, chemical_id, is_planned, is_completed)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertMaintenance.run(1, '2024-01-10', 'pruning', 'Winter pruning - removed dead branches', null, null, null, 0, 1);
|
|
insertMaintenance.run(2, '2024-01-05', 'fertilizer', 'Applied organic compost', '2 cups', 2, null, 0, 1);
|
|
insertMaintenance.run(3, '2024-05-15', 'watering', 'Deep watering during dry spell', '1 gallon', null, null, 0, 1);
|
|
insertMaintenance.run(1, '2024-03-20', 'chemical', 'Applied neem oil for aphid prevention', '2 tablespoons per gallon', null, 1, 0, 1);
|
|
|
|
// Sample harvest records
|
|
const insertHarvest = db.prepare(`
|
|
INSERT INTO harvest_records (plant_id, date, quantity, unit, notes)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertHarvest.run(1, '2023-09-15', 25, 'lbs', 'Excellent harvest, apples were sweet and crisp');
|
|
insertHarvest.run(2, '2023-07-20', 3, 'cups', 'First harvest of the season, berries were plump and sweet');
|
|
insertHarvest.run(3, '2024-05-25', 0.5, 'cups', 'Fresh basil for cooking, very aromatic');
|
|
|
|
// Sample observations
|
|
const insertObservation = db.prepare(`
|
|
INSERT INTO plant_observations (plant_id, date, title, observation, weather_conditions, temperature, tags)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertObservation.run(1, '2024-01-15', 'Winter dormancy check', 'Apple tree showing normal winter dormancy. Buds are tight and healthy looking. No signs of pest damage on bark.', 'Clear, cold', 2.5, 'dormancy,health-check,winter');
|
|
insertObservation.run(2, '2024-01-20', 'Pruning completed', 'Finished annual pruning of blueberry bushes. Removed about 20% of old wood and opened up center for better air circulation.', 'Overcast', 8.0, 'pruning,maintenance');
|
|
insertObservation.run(3, '2024-05-12', 'First true leaves', 'Basil seedlings showing first set of true leaves. Growth is vigorous and color is deep green. Ready for transplanting soon.', 'Sunny', 22.0, 'growth,seedling,transplant-ready');
|
|
console.log('Sample data inserted successfully!');
|
|
} catch (err) {
|
|
console.error('Error inserting sample data:', err);
|
|
}
|
|
}
|
|
|
|
// Routes
|
|
|
|
// Photo upload endpoint
|
|
app.post('/api/upload-photo', upload.single('photo'), (req, res) => {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).json({ error: 'No file uploaded' });
|
|
}
|
|
|
|
// Return the file path relative to the server
|
|
const photoUrl = `/uploads/${req.file.filename}`;
|
|
res.json({ photoUrl });
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Plant Observations
|
|
app.get('/api/observations', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('SELECT * FROM plant_observations ORDER BY date DESC');
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
date: row.date,
|
|
title: row.title,
|
|
observation: row.observation,
|
|
weatherConditions: row.weather_conditions,
|
|
temperature: row.temperature,
|
|
photoUrl: row.photo_url,
|
|
tags: row.tags,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/observations', (req, res) => {
|
|
try {
|
|
const { plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO plant_observations (plant_id, date, title, observation, weather_conditions, temperature, photo_url, tags)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags);
|
|
|
|
const getStmt = db.prepare('SELECT * FROM plant_observations WHERE id = ?');
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
date: row.date,
|
|
title: row.title,
|
|
observation: row.observation,
|
|
weatherConditions: row.weather_conditions,
|
|
temperature: row.temperature,
|
|
photoUrl: row.photo_url,
|
|
tags: row.tags,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.put('/api/observations/:id', (req, res) => {
|
|
try {
|
|
const { plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
UPDATE plant_observations SET plant_id = ?, date = ?, title = ?, observation = ?,
|
|
weather_conditions = ?, temperature = ?, photo_url = ?, tags = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`);
|
|
|
|
stmt.run(plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags, req.params.id);
|
|
|
|
const getStmt = db.prepare('SELECT * FROM plant_observations WHERE id = ?');
|
|
const row = getStmt.get(req.params.id);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
date: row.date,
|
|
title: row.title,
|
|
observation: row.observation,
|
|
weatherConditions: row.weather_conditions,
|
|
temperature: row.temperature,
|
|
photoUrl: row.photo_url,
|
|
tags: row.tags,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/observations/:id', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('DELETE FROM plant_observations WHERE id = ?');
|
|
stmt.run(req.params.id);
|
|
res.json({ message: 'Observation deleted successfully' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Plants
|
|
app.get('/api/plants', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('SELECT * FROM plants ORDER BY created_at DESC');
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
type: row.type,
|
|
variety: row.variety,
|
|
purchaseLocation: row.purchase_location,
|
|
seedlingAge: row.seedling_age,
|
|
seedlingHeight: row.seedling_height,
|
|
plantingDate: row.planting_date,
|
|
healthStatus: row.health_status,
|
|
currentHeight: row.current_height,
|
|
photoUrl: row.photo_url,
|
|
currentPhotoUrl: row.current_photo_url,
|
|
estimatedRipeningStart: row.estimated_ripening_start,
|
|
estimatedRipeningEnd: row.estimated_ripening_end,
|
|
ripeningNotes: row.ripening_notes,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/plants', (req, res) => {
|
|
try {
|
|
const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, photo_url, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes);
|
|
|
|
// Get the created plant
|
|
const getStmt = db.prepare('SELECT * FROM plants WHERE id = ?');
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
type: row.type,
|
|
variety: row.variety,
|
|
purchaseLocation: row.purchase_location,
|
|
seedlingAge: row.seedling_age,
|
|
seedlingHeight: row.seedling_height,
|
|
plantingDate: row.planting_date,
|
|
healthStatus: row.health_status,
|
|
currentHeight: row.current_height,
|
|
photoUrl: row.photo_url,
|
|
currentPhotoUrl: row.current_photo_url,
|
|
estimatedRipeningStart: row.estimated_ripening_start,
|
|
estimatedRipeningEnd: row.estimated_ripening_end,
|
|
ripeningNotes: row.ripening_notes,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.put('/api/plants/:id', (req, res) => {
|
|
try {
|
|
const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
UPDATE plants SET type = ?, variety = ?, purchase_location = ?, seedling_age = ?, seedling_height = ?, planting_date = ?,
|
|
health_status = ?, current_height = ?, photo_url = ?, notes = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`);
|
|
|
|
stmt.run(type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes, req.params.id);
|
|
|
|
// Get the updated plant
|
|
const getStmt = db.prepare('SELECT * FROM plants WHERE id = ?');
|
|
const row = getStmt.get(req.params.id);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
type: row.type,
|
|
variety: row.variety,
|
|
purchaseLocation: row.purchase_location,
|
|
seedlingAge: row.seedling_age,
|
|
seedlingHeight: row.seedling_height,
|
|
plantingDate: row.planting_date,
|
|
healthStatus: row.health_status,
|
|
currentHeight: row.current_height,
|
|
photoUrl: row.photo_url,
|
|
currentPhotoUrl: row.current_photo_url,
|
|
estimatedRipeningStart: row.estimated_ripening_start,
|
|
estimatedRipeningEnd: row.estimated_ripening_end,
|
|
ripeningNotes: row.ripening_notes,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/plants/:id', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('DELETE FROM plants WHERE id = ?');
|
|
stmt.run(req.params.id);
|
|
res.json({ message: 'Plant deleted successfully' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Tasks
|
|
app.get('/api/tasks', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare(`
|
|
SELECT t.*, f.name as fertilizer_name, c.name as chemical_name
|
|
FROM tasks t
|
|
LEFT JOIN fertilizers f ON t.fertilizer_id = f.id
|
|
LEFT JOIN chemicals c ON t.chemical_id = c.id
|
|
ORDER BY t.deadline ASC
|
|
`);
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
type: row.type,
|
|
title: row.title,
|
|
description: row.description,
|
|
deadline: row.deadline,
|
|
completed: Boolean(row.completed),
|
|
fertilizerId: row.fertilizer_id,
|
|
chemicalId: row.chemical_id,
|
|
fertilizerName: row.fertilizer_name,
|
|
chemicalName: row.chemical_name,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/tasks', (req, res) => {
|
|
try {
|
|
const { plantId, type, title, description, deadline, completed = false, fertilizerId, chemicalId } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO tasks (plant_id, type, title, description, deadline, completed, fertilizer_id, chemical_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(plantId, type, title, description, deadline, completed ? 1 : 0, fertilizerId || null, chemicalId || null);
|
|
|
|
const getStmt = db.prepare(`
|
|
SELECT t.*, f.name as fertilizer_name, c.name as chemical_name
|
|
FROM tasks t
|
|
LEFT JOIN fertilizers f ON t.fertilizer_id = f.id
|
|
LEFT JOIN chemicals c ON t.chemical_id = c.id
|
|
WHERE t.id = ?
|
|
`);
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
type: row.type,
|
|
title: row.title,
|
|
description: row.description,
|
|
deadline: row.deadline,
|
|
completed: Boolean(row.completed),
|
|
fertilizerId: row.fertilizer_id,
|
|
chemicalId: row.chemical_id,
|
|
fertilizerName: row.fertilizer_name,
|
|
chemicalName: row.chemical_name,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.put('/api/tasks/:id', (req, res) => {
|
|
try {
|
|
const { plantId, type, title, description, deadline, completed, fertilizerId, chemicalId } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
UPDATE tasks SET plant_id = ?, type = ?, title = ?, description = ?,
|
|
deadline = ?, completed = ?, fertilizer_id = ?, chemical_id = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`);
|
|
|
|
stmt.run(plantId, type, title, description, deadline, completed ? 1 : 0, fertilizerId || null, chemicalId || null, req.params.id);
|
|
|
|
const getStmt = db.prepare(`
|
|
SELECT t.*, f.name as fertilizer_name, c.name as chemical_name
|
|
FROM tasks t
|
|
LEFT JOIN fertilizers f ON t.fertilizer_id = f.id
|
|
LEFT JOIN chemicals c ON t.chemical_id = c.id
|
|
WHERE t.id = ?
|
|
`);
|
|
const row = getStmt.get(req.params.id);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
type: row.type,
|
|
title: row.title,
|
|
description: row.description,
|
|
deadline: row.deadline,
|
|
completed: Boolean(row.completed),
|
|
fertilizerId: row.fertilizer_id,
|
|
chemicalId: row.chemical_id,
|
|
fertilizerName: row.fertilizer_name,
|
|
chemicalName: row.chemical_name,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/tasks/:id', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('DELETE FROM tasks WHERE id = ?');
|
|
stmt.run(req.params.id);
|
|
res.json({ message: 'Task deleted successfully' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Maintenance Records
|
|
app.get('/api/maintenance', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare(`
|
|
SELECT mr.*, f.name as fertilizer_name, c.name as chemical_name
|
|
FROM maintenance_records mr
|
|
LEFT JOIN fertilizers f ON mr.fertilizer_id = f.id
|
|
LEFT JOIN chemicals c ON mr.chemical_id = c.id
|
|
ORDER BY mr.date DESC
|
|
`);
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
date: row.date,
|
|
type: row.type,
|
|
description: row.description,
|
|
amount: row.amount,
|
|
fertilizerId: row.fertilizer_id,
|
|
chemicalId: row.chemical_id,
|
|
fertilizerName: row.fertilizer_name,
|
|
chemicalName: row.chemical_name,
|
|
isPlanned: Boolean(row.is_planned),
|
|
isCompleted: Boolean(row.is_completed),
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/maintenance', (req, res) => {
|
|
try {
|
|
const { plantId, date, type, description, amount, fertilizerId, chemicalId, isPlanned, isCompleted } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO maintenance_records (plant_id, date, type, description, amount, fertilizer_id, chemical_id, is_planned, is_completed)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(plantId, date, type, description, amount, fertilizerId || null, chemicalId || null, isPlanned ? 1 : 0, isCompleted ? 1 : 0);
|
|
|
|
const getStmt = db.prepare(`
|
|
SELECT mr.*, f.name as fertilizer_name, c.name as chemical_name
|
|
FROM maintenance_records mr
|
|
LEFT JOIN fertilizers f ON mr.fertilizer_id = f.id
|
|
LEFT JOIN chemicals c ON mr.chemical_id = c.id
|
|
WHERE mr.id = ?
|
|
`);
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
date: row.date,
|
|
type: row.type,
|
|
description: row.description,
|
|
amount: row.amount,
|
|
fertilizerId: row.fertilizer_id,
|
|
chemicalId: row.chemical_id,
|
|
fertilizerName: row.fertilizer_name,
|
|
chemicalName: row.chemical_name,
|
|
isPlanned: Boolean(row.is_planned),
|
|
isCompleted: Boolean(row.is_completed),
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Harvest Records
|
|
app.get('/api/harvests', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('SELECT * FROM harvest_records ORDER BY date DESC');
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
date: row.date,
|
|
quantity: row.quantity,
|
|
unit: row.unit,
|
|
weight: row.weight,
|
|
weightUnit: row.weight_unit,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/harvests', (req, res) => {
|
|
try {
|
|
const { plantId, date, quantity, unit, weight, weightUnit, notes } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO harvest_records (plant_id, date, quantity, unit, weight, weight_unit, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(plantId, date, quantity, unit, weight || null, weightUnit || null, notes);
|
|
|
|
const getStmt = db.prepare('SELECT * FROM harvest_records WHERE id = ? ORDER BY date DESC');
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
date: row.date,
|
|
quantity: row.quantity,
|
|
unit: row.unit,
|
|
weight: row.weight,
|
|
weightUnit: row.weight_unit,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Harvest Processing
|
|
app.get('/api/harvest-processing', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare(`
|
|
SELECT hp.*, hr.plant_id, hr.date as harvest_date
|
|
FROM harvest_processing hp
|
|
JOIN harvest_records hr ON hp.harvest_id = hr.id
|
|
ORDER BY hp.process_date DESC
|
|
`);
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
harvestId: row.harvest_id,
|
|
plantId: row.plant_id,
|
|
harvestDate: row.harvest_date,
|
|
processType: row.process_type,
|
|
quantity: row.quantity,
|
|
unit: row.unit,
|
|
processDate: row.process_date,
|
|
expiryDate: row.expiry_date,
|
|
storageLocation: row.storage_location,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/harvest-processing', (req, res) => {
|
|
try {
|
|
const { harvestId, processType, quantity, unit, processDate, expiryDate, storageLocation, notes } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO harvest_processing (harvest_id, process_type, quantity, unit, process_date, expiry_date, storage_location, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(harvestId, processType, quantity, unit, processDate, expiryDate, storageLocation, notes);
|
|
|
|
// Create corresponding stock entry
|
|
const stockStmt = db.prepare(`
|
|
INSERT INTO harvest_stock (processing_id, current_quantity, unit, last_updated, status)
|
|
VALUES (?, ?, ?, ?, 'available')
|
|
`);
|
|
stockStmt.run(result.lastInsertRowid, quantity, unit, processDate);
|
|
|
|
const getStmt = db.prepare(`
|
|
SELECT hp.*, hr.plant_id, hr.date as harvest_date
|
|
FROM harvest_processing hp
|
|
JOIN harvest_records hr ON hp.harvest_id = hr.id
|
|
WHERE hp.id = ?
|
|
`);
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
harvestId: row.harvest_id,
|
|
plantId: row.plant_id,
|
|
harvestDate: row.harvest_date,
|
|
processType: row.process_type,
|
|
quantity: row.quantity,
|
|
unit: row.unit,
|
|
processDate: row.process_date,
|
|
expiryDate: row.expiry_date,
|
|
storageLocation: row.storage_location,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Harvest Stock
|
|
app.get('/api/harvest-stock', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare(`
|
|
SELECT hs.*, hp.process_type, hp.harvest_id, hr.plant_id, hr.date as harvest_date
|
|
FROM harvest_stock hs
|
|
JOIN harvest_processing hp ON hs.processing_id = hp.id
|
|
JOIN harvest_records hr ON hp.harvest_id = hr.id
|
|
ORDER BY hs.last_updated DESC
|
|
`);
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
processingId: row.processing_id,
|
|
harvestId: row.harvest_id,
|
|
plantId: row.plant_id,
|
|
harvestDate: row.harvest_date,
|
|
processType: row.process_type,
|
|
currentQuantity: row.current_quantity,
|
|
unit: row.unit,
|
|
lastUpdated: row.last_updated,
|
|
status: row.status,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.put('/api/harvest-stock/:id', (req, res) => {
|
|
try {
|
|
const { currentQuantity, status, notes } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
UPDATE harvest_stock SET current_quantity = ?, status = ?, notes = ?,
|
|
last_updated = date('now'), updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`);
|
|
|
|
stmt.run(currentQuantity, status, notes, req.params.id);
|
|
|
|
const getStmt = db.prepare(`
|
|
SELECT hs.*, hp.process_type, hp.harvest_id, hr.plant_id, hr.date as harvest_date
|
|
FROM harvest_stock hs
|
|
JOIN harvest_processing hp ON hs.processing_id = hp.id
|
|
JOIN harvest_records hr ON hp.harvest_id = hr.id
|
|
WHERE hs.id = ?
|
|
`);
|
|
const row = getStmt.get(req.params.id);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
processingId: row.processing_id,
|
|
harvestId: row.harvest_id,
|
|
plantId: row.plant_id,
|
|
harvestDate: row.harvest_date,
|
|
processType: row.process_type,
|
|
currentQuantity: row.current_quantity,
|
|
unit: row.unit,
|
|
lastUpdated: row.last_updated,
|
|
status: row.status,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Health check
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({ status: 'OK', message: 'GardenTrack API is running' });
|
|
});
|
|
|
|
// Fertilizers
|
|
app.get('/api/fertilizers', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('SELECT * FROM fertilizers ORDER BY name ASC');
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
name: row.name,
|
|
brand: row.brand,
|
|
type: row.type,
|
|
npkRatio: row.npk_ratio,
|
|
description: row.description,
|
|
applicationRate: row.application_rate,
|
|
frequency: row.frequency,
|
|
season: row.season,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/fertilizers', (req, res) => {
|
|
try {
|
|
const { name, brand, type, npkRatio, description, applicationRate, frequency, season, notes } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO fertilizers (name, brand, type, npk_ratio, description, application_rate, frequency, season, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(name, brand, type, npkRatio, description, applicationRate, frequency, season, notes);
|
|
|
|
const getStmt = db.prepare('SELECT * FROM fertilizers WHERE id = ?');
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
name: row.name,
|
|
brand: row.brand,
|
|
type: row.type,
|
|
npkRatio: row.npk_ratio,
|
|
description: row.description,
|
|
applicationRate: row.application_rate,
|
|
frequency: row.frequency,
|
|
season: row.season,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Chemicals
|
|
app.get('/api/chemicals', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('SELECT * FROM chemicals ORDER BY name ASC');
|
|
const rows = stmt.all();
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
name: row.name,
|
|
brand: row.brand,
|
|
type: row.type,
|
|
activeIngredient: row.active_ingredient,
|
|
concentration: row.concentration,
|
|
targetPests: row.target_pests,
|
|
applicationMethod: row.application_method,
|
|
safetyPeriod: row.safety_period,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/chemicals', (req, res) => {
|
|
try {
|
|
const { name, brand, type, activeIngredient, concentration, targetPests, applicationMethod, safetyPeriod, notes } = req.body;
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO chemicals (name, brand, type, active_ingredient, concentration, target_pests, application_method, safety_period, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(name, brand, type, activeIngredient, concentration, targetPests, applicationMethod, safetyPeriod, notes);
|
|
|
|
const getStmt = db.prepare('SELECT * FROM chemicals WHERE id = ?');
|
|
const row = getStmt.get(result.lastInsertRowid);
|
|
|
|
res.json({
|
|
id: row.id,
|
|
name: row.name,
|
|
brand: row.brand,
|
|
type: row.type,
|
|
activeIngredient: row.active_ingredient,
|
|
concentration: row.concentration,
|
|
targetPests: row.target_pests,
|
|
applicationMethod: row.application_method,
|
|
safetyPeriod: row.safety_period,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
// Additional endpoints for enhanced functionality
|
|
|
|
// Get plant by ID
|
|
app.get('/api/plants/:id', (req, res) => {
|
|
try {
|
|
const stmt = db.prepare('SELECT * FROM plants WHERE id = ?');
|
|
const row = stmt.get(req.params.id);
|
|
|
|
if (!row) {
|
|
res.status(404).json({ error: 'Plant not found' });
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
id: row.id,
|
|
type: row.type,
|
|
variety: row.variety,
|
|
purchaseLocation: row.purchase_location,
|
|
seedlingAge: row.seedling_age,
|
|
seedlingHeight: row.seedling_height,
|
|
plantingDate: row.planting_date,
|
|
healthStatus: row.health_status,
|
|
currentHeight: row.current_height,
|
|
photoUrl: row.photo_url,
|
|
currentPhotoUrl: row.current_photo_url,
|
|
estimatedRipeningStart: row.estimated_ripening_start,
|
|
estimatedRipeningEnd: row.estimated_ripening_end,
|
|
ripeningNotes: row.ripening_notes,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Search plants
|
|
app.get('/api/plants/search', (req, res) => {
|
|
try {
|
|
const query = req.query.q;
|
|
if (!query) {
|
|
res.status(400).json({ error: 'Search query is required' });
|
|
return;
|
|
}
|
|
|
|
const stmt = db.prepare(`
|
|
SELECT * FROM plants WHERE variety LIKE ? OR type LIKE ? OR notes LIKE ? ORDER BY created_at DESC
|
|
`);
|
|
const searchTerm = `%${query}%`;
|
|
const rows = stmt.all(searchTerm, searchTerm, searchTerm);
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
type: row.type,
|
|
variety: row.variety,
|
|
purchaseLocation: row.purchase_location,
|
|
seedlingAge: row.seedling_age,
|
|
seedlingHeight: row.seedling_height,
|
|
plantingDate: row.planting_date,
|
|
healthStatus: row.health_status,
|
|
currentHeight: row.current_height,
|
|
photoUrl: row.photo_url,
|
|
currentPhotoUrl: row.current_photo_url,
|
|
estimatedRipeningStart: row.estimated_ripening_start,
|
|
estimatedRipeningEnd: row.estimated_ripening_end,
|
|
ripeningNotes: row.ripening_notes,
|
|
notes: row.notes,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Get upcoming tasks
|
|
app.get('/api/tasks/upcoming', (req, res) => {
|
|
try {
|
|
const days = parseInt(req.query.days) || 7;
|
|
const endDate = new Date();
|
|
endDate.setDate(endDate.getDate() + days);
|
|
|
|
const stmt = db.prepare('SELECT * FROM tasks WHERE completed = 0 AND deadline <= ? ORDER BY deadline ASC');
|
|
const rows = stmt.all(endDate.toISOString().split('T')[0]);
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
title: row.title,
|
|
description: row.description,
|
|
deadline: row.deadline,
|
|
completed: Boolean(row.completed),
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Get overdue tasks
|
|
app.get('/api/tasks/overdue', (req, res) => {
|
|
try {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const stmt = db.prepare('SELECT * FROM tasks WHERE completed = 0 AND deadline < ? ORDER BY deadline ASC');
|
|
const rows = stmt.all(today);
|
|
|
|
res.json(rows.map(row => ({
|
|
id: row.id,
|
|
plantId: row.plant_id,
|
|
title: row.title,
|
|
description: row.description,
|
|
deadline: row.deadline,
|
|
completed: Boolean(row.completed),
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Dashboard summary
|
|
app.get('/api/dashboard/summary', (req, res) => {
|
|
try {
|
|
const results = {
|
|
totalPlants: db.prepare('SELECT COUNT(*) as count FROM plants').get().count,
|
|
healthyPlants: db.prepare('SELECT COUNT(*) as count FROM plants WHERE health_status = "good"').get().count,
|
|
plantsNeedingAttention: db.prepare('SELECT COUNT(*) as count FROM plants WHERE health_status = "needs-attention"').get().count,
|
|
totalTasks: db.prepare('SELECT COUNT(*) as count FROM tasks').get().count,
|
|
completedTasks: db.prepare('SELECT COUNT(*) as count FROM tasks WHERE completed = 1').get().count,
|
|
upcomingTasks: db.prepare('SELECT COUNT(*) as count FROM tasks WHERE completed = 0 AND deadline >= date("now")').get().count,
|
|
totalHarvests: db.prepare('SELECT COUNT(*) as count FROM harvest_records').get().count,
|
|
recentMaintenanceCount: db.prepare('SELECT COUNT(*) as count FROM maintenance_records WHERE date >= date("now", "-30 days")').get().count
|
|
};
|
|
|
|
res.json(results);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Error handling middleware
|
|
app.use((err, req, res, next) => {
|
|
console.error(err.stack);
|
|
res.status(500).json({ error: 'Something went wrong!' });
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, () => {
|
|
console.log(`GardenTrack API server running on port ${PORT}`);
|
|
});
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGINT', () => {
|
|
if (db) {
|
|
db.close();
|
|
console.log('Database connection closed.');
|
|
}
|
|
process.exit(0);
|
|
}); |