787 lines
25 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');
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 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 }));
// 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,
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
console.log('Plants 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');
// 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,
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,
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
)
`);
console.log('Maintenance records table ready');
// Tasks table
db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plant_id INTEGER,
title TEXT NOT NULL,
description TEXT,
deadline DATE NOT NULL,
completed BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (plant_id) REFERENCES plants (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 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, is_planned, is_completed)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
insertMaintenance.run(1, '2024-01-10', 'pruning', 'Winter pruning - removed dead branches', null, 0, 1);
insertMaintenance.run(2, '2024-01-05', 'fertilizer', 'Applied organic compost', '2 cups', 0, 1);
insertMaintenance.run(3, '2024-05-15', 'watering', 'Deep watering during dry spell', '1 gallon', 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
// 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,
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,
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,
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 * FROM tasks ORDER BY deadline ASC');
const rows = stmt.all();
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 });
}
});
app.post('/api/tasks', (req, res) => {
try {
const { plantId, title, description, deadline, completed = false } = req.body;
const stmt = db.prepare(`
INSERT INTO tasks (plant_id, title, description, deadline, completed)
VALUES (?, ?, ?, ?, ?)
`);
const result = stmt.run(plantId, title, description, deadline, completed ? 1 : 0);
const getStmt = db.prepare('SELECT * FROM tasks WHERE id = ?');
const row = getStmt.get(result.lastInsertRowid);
res.json({
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(400).json({ error: err.message });
}
});
app.put('/api/tasks/:id', (req, res) => {
try {
const { plantId, title, description, deadline, completed } = req.body;
const stmt = db.prepare(`
UPDATE tasks SET plant_id = ?, title = ?, description = ?,
deadline = ?, completed = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
stmt.run(plantId, title, description, deadline, completed ? 1 : 0, req.params.id);
const getStmt = db.prepare('SELECT * FROM tasks WHERE id = ?');
const row = getStmt.get(req.params.id);
res.json({
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(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 * FROM maintenance_records ORDER BY 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,
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, isPlanned, isCompleted } = req.body;
const stmt = db.prepare(`
INSERT INTO maintenance_records (plant_id, date, type, description, amount, is_planned, is_completed)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(plantId, date, type, description, amount, isPlanned ? 1 : 0, isCompleted ? 1 : 0);
const getStmt = db.prepare('SELECT * FROM maintenance_records WHERE 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,
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,
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, notes } = req.body;
const stmt = db.prepare(`
INSERT INTO harvest_records (plant_id, date, quantity, unit, notes)
VALUES (?, ?, ?, ?, ?)
`);
const result = stmt.run(plantId, date, quantity, unit, notes);
const getStmt = db.prepare('SELECT * FROM harvest_records WHERE id = ?');
const row = getStmt.get(result.lastInsertRowid);
res.json({
id: row.id,
plantId: row.plant_id,
date: row.date,
quantity: row.quantity,
unit: row.unit,
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' });
});
// 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,
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,
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);
});