673 lines
22 KiB
JavaScript
673 lines
22 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const bodyParser = require('body-parser');
|
|
const sqlite3 = require('sqlite3').verbose();
|
|
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
|
|
const db = new sqlite3.Database(DB_PATH, (err) => {
|
|
if (err) {
|
|
console.error('Error opening database:', err.message);
|
|
process.exit(1);
|
|
} else {
|
|
console.log('Connected to SQLite database at:', DB_PATH);
|
|
initializeDatabase();
|
|
}
|
|
});
|
|
|
|
// Initialize database tables
|
|
function initializeDatabase() {
|
|
console.log('Initializing database tables...');
|
|
|
|
db.serialize(() => {
|
|
// Plants table
|
|
db.run(`
|
|
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
|
|
)
|
|
`, (err) => {
|
|
if (err) console.error('Error creating plants table:', err);
|
|
else console.log('Plants table ready');
|
|
});
|
|
|
|
// Plant history table
|
|
db.run(`
|
|
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
|
|
)
|
|
`, (err) => {
|
|
if (err) console.error('Error creating plant_history table:', err);
|
|
else console.log('Plant history table ready');
|
|
});
|
|
|
|
// Harvest records table
|
|
db.run(`
|
|
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
|
|
)
|
|
`, (err) => {
|
|
if (err) console.error('Error creating harvest_records table:', err);
|
|
else console.log('Harvest records table ready');
|
|
});
|
|
|
|
// Maintenance records table
|
|
db.run(`
|
|
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
|
|
)
|
|
`, (err) => {
|
|
if (err) console.error('Error creating maintenance_records table:', err);
|
|
else console.log('Maintenance records table ready');
|
|
});
|
|
|
|
// Tasks table
|
|
db.run(`
|
|
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
|
|
)
|
|
`, (err) => {
|
|
if (err) console.error('Error creating tasks table:', err);
|
|
else console.log('Tasks table ready');
|
|
});
|
|
|
|
// Insert sample data if tables are empty
|
|
db.get('SELECT COUNT(*) as count FROM plants', (err, row) => {
|
|
if (err) {
|
|
console.error('Error checking plants table:', err);
|
|
return;
|
|
}
|
|
|
|
if (row.count === 0) {
|
|
console.log('Inserting sample data...');
|
|
insertSampleData();
|
|
} else {
|
|
console.log('Database already contains data');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Insert sample data
|
|
function insertSampleData() {
|
|
// Sample plants
|
|
const plantSql = `INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
db.run(plantSql, ['tree', 'Apple - Honeycrisp', 'Local Nursery', 12, 45.5, '2022-04-15', 'good', 180.2, 'Growing well, good fruit production']);
|
|
db.run(plantSql, ['shrub', 'Blueberry - Bluecrop', 'Garden Center', 8, 25.0, '2023-03-20', 'needs-attention', 65.8, 'Leaves showing slight discoloration']);
|
|
db.run(plantSql, ['herb', 'Basil - Sweet Genovese', 'Online Store', 3, 8.2, '2024-05-10', 'good', 22.5, 'Producing well, regular harvests']);
|
|
|
|
// Sample tasks
|
|
const taskSql = `INSERT INTO tasks (plant_id, title, description, deadline, completed) VALUES (?, ?, ?, ?, ?)`;
|
|
db.run(taskSql, [1, 'Apply fertilizer', 'Apply spring fertilizer to apple trees', '2024-03-15', 0]);
|
|
db.run(taskSql, [2, 'Prune blueberry bushes', 'Annual pruning before spring growth', '2024-02-28', 1]);
|
|
db.run(taskSql, [3, 'Harvest basil', 'Regular harvest to encourage growth', '2024-06-01', 0]);
|
|
|
|
// Sample maintenance records
|
|
const maintenanceSql = `INSERT INTO maintenance_records (plant_id, date, type, description, amount, is_planned, is_completed) VALUES (?, ?, ?, ?, ?, ?, ?)`;
|
|
db.run(maintenanceSql, [1, '2024-01-10', 'pruning', 'Winter pruning - removed dead branches', null, 0, 1]);
|
|
db.run(maintenanceSql, [2, '2024-01-05', 'fertilizer', 'Applied organic compost', '2 cups', 0, 1]);
|
|
db.run(maintenanceSql, [3, '2024-05-15', 'watering', 'Deep watering during dry spell', '1 gallon', 0, 1]);
|
|
|
|
// Sample harvest records
|
|
const harvestSql = `INSERT INTO harvest_records (plant_id, date, quantity, unit, notes) VALUES (?, ?, ?, ?, ?)`;
|
|
db.run(harvestSql, [1, '2023-09-15', 25, 'lbs', 'Excellent harvest, apples were sweet and crisp']);
|
|
db.run(harvestSql, [2, '2023-07-20', 3, 'cups', 'First harvest of the season, berries were plump and sweet']);
|
|
db.run(harvestSql, [3, '2024-05-25', 0.5, 'cups', 'Fresh basil for cooking, very aromatic']);
|
|
|
|
console.log('Sample data inserted successfully!');
|
|
}
|
|
|
|
// Routes
|
|
|
|
// Plants
|
|
app.get('/api/plants', (req, res) => {
|
|
const sql = 'SELECT * FROM plants ORDER BY created_at DESC';
|
|
db.all(sql, [], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
})));
|
|
});
|
|
});
|
|
|
|
app.post('/api/plants', (req, res) => {
|
|
const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body;
|
|
const sql = `INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, photo_url, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
|
|
db.run(sql, [type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes], function(err) {
|
|
if (err) {
|
|
res.status(400).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
// Get the created plant
|
|
db.get('SELECT * FROM plants WHERE id = ?', [this.lastID], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
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
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
app.put('/api/plants/:id', (req, res) => {
|
|
const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body;
|
|
const sql = `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 = ?`;
|
|
|
|
db.run(sql, [type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes, req.params.id], function(err) {
|
|
if (err) {
|
|
res.status(400).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
// Get the updated plant
|
|
db.get('SELECT * FROM plants WHERE id = ?', [req.params.id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
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
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
app.delete('/api/plants/:id', (req, res) => {
|
|
db.run('DELETE FROM plants WHERE id = ?', [req.params.id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({ message: 'Plant deleted successfully' });
|
|
});
|
|
});
|
|
|
|
// Tasks
|
|
app.get('/api/tasks', (req, res) => {
|
|
const sql = 'SELECT * FROM tasks ORDER BY deadline ASC';
|
|
db.all(sql, [], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
})));
|
|
});
|
|
});
|
|
|
|
app.post('/api/tasks', (req, res) => {
|
|
const { plantId, title, description, deadline, completed = false } = req.body;
|
|
const sql = `INSERT INTO tasks (plant_id, title, description, deadline, completed)
|
|
VALUES (?, ?, ?, ?, ?)`;
|
|
|
|
db.run(sql, [plantId, title, description, deadline, completed ? 1 : 0], function(err) {
|
|
if (err) {
|
|
res.status(400).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
db.get('SELECT * FROM tasks WHERE id = ?', [this.lastID], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
app.put('/api/tasks/:id', (req, res) => {
|
|
const { plantId, title, description, deadline, completed } = req.body;
|
|
const sql = `UPDATE tasks SET plant_id = ?, title = ?, description = ?,
|
|
deadline = ?, completed = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?`;
|
|
|
|
db.run(sql, [plantId, title, description, deadline, completed ? 1 : 0, req.params.id], function(err) {
|
|
if (err) {
|
|
res.status(400).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
db.get('SELECT * FROM tasks WHERE id = ?', [req.params.id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
app.delete('/api/tasks/:id', (req, res) => {
|
|
db.run('DELETE FROM tasks WHERE id = ?', [req.params.id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({ message: 'Task deleted successfully' });
|
|
});
|
|
});
|
|
|
|
// Maintenance Records
|
|
app.get('/api/maintenance', (req, res) => {
|
|
const sql = 'SELECT * FROM maintenance_records ORDER BY date DESC';
|
|
db.all(sql, [], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
})));
|
|
});
|
|
});
|
|
|
|
app.post('/api/maintenance', (req, res) => {
|
|
const { plantId, date, type, description, amount, isPlanned, isCompleted } = req.body;
|
|
const sql = `INSERT INTO maintenance_records (plant_id, date, type, description, amount, is_planned, is_completed)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`;
|
|
|
|
db.run(sql, [plantId, date, type, description, amount, isPlanned ? 1 : 0, isCompleted ? 1 : 0], function(err) {
|
|
if (err) {
|
|
res.status(400).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
db.get('SELECT * FROM maintenance_records WHERE id = ?', [this.lastID], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Harvest Records
|
|
app.get('/api/harvests', (req, res) => {
|
|
const sql = 'SELECT * FROM harvest_records ORDER BY date DESC';
|
|
db.all(sql, [], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
})));
|
|
});
|
|
});
|
|
|
|
app.post('/api/harvests', (req, res) => {
|
|
const { plantId, date, quantity, unit, notes } = req.body;
|
|
const sql = `INSERT INTO harvest_records (plant_id, date, quantity, unit, notes)
|
|
VALUES (?, ?, ?, ?, ?)`;
|
|
|
|
db.run(sql, [plantId, date, quantity, unit, notes], function(err) {
|
|
if (err) {
|
|
res.status(400).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
db.get('SELECT * FROM harvest_records WHERE id = ?', [this.lastID], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// 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) => {
|
|
const sql = 'SELECT * FROM plants WHERE id = ?';
|
|
db.get(sql, [req.params.id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
});
|
|
});
|
|
});
|
|
|
|
// Search plants
|
|
app.get('/api/plants/search', (req, res) => {
|
|
const query = req.query.q;
|
|
if (!query) {
|
|
res.status(400).json({ error: 'Search query is required' });
|
|
return;
|
|
}
|
|
|
|
const sql = `SELECT * FROM plants WHERE variety LIKE ? OR type LIKE ? OR notes LIKE ? ORDER BY created_at DESC`;
|
|
const searchTerm = `%${query}%`;
|
|
|
|
db.all(sql, [searchTerm, searchTerm, searchTerm], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
})));
|
|
});
|
|
});
|
|
|
|
// Get upcoming tasks
|
|
app.get('/api/tasks/upcoming', (req, res) => {
|
|
const days = parseInt(req.query.days) || 7;
|
|
const endDate = new Date();
|
|
endDate.setDate(endDate.getDate() + days);
|
|
|
|
const sql = 'SELECT * FROM tasks WHERE completed = 0 AND deadline <= ? ORDER BY deadline ASC';
|
|
db.all(sql, [endDate.toISOString().split('T')[0]], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
})));
|
|
});
|
|
});
|
|
|
|
// Get overdue tasks
|
|
app.get('/api/tasks/overdue', (req, res) => {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const sql = 'SELECT * FROM tasks WHERE completed = 0 AND deadline < ? ORDER BY deadline ASC';
|
|
|
|
db.all(sql, [today], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
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
|
|
})));
|
|
});
|
|
});
|
|
|
|
// Dashboard summary
|
|
app.get('/api/dashboard/summary', (req, res) => {
|
|
const queries = {
|
|
totalPlants: 'SELECT COUNT(*) as count FROM plants',
|
|
healthyPlants: 'SELECT COUNT(*) as count FROM plants WHERE health_status = "good"',
|
|
plantsNeedingAttention: 'SELECT COUNT(*) as count FROM plants WHERE health_status = "needs-attention"',
|
|
totalTasks: 'SELECT COUNT(*) as count FROM tasks',
|
|
completedTasks: 'SELECT COUNT(*) as count FROM tasks WHERE completed = 1',
|
|
upcomingTasks: 'SELECT COUNT(*) as count FROM tasks WHERE completed = 0 AND deadline >= date("now")',
|
|
totalHarvests: 'SELECT COUNT(*) as count FROM harvest_records',
|
|
recentMaintenanceCount: 'SELECT COUNT(*) as count FROM maintenance_records WHERE date >= date("now", "-30 days")'
|
|
};
|
|
|
|
const results = {};
|
|
const queryKeys = Object.keys(queries);
|
|
let completed = 0;
|
|
|
|
queryKeys.forEach(key => {
|
|
db.get(queries[key], [], (err, row) => {
|
|
if (err) {
|
|
console.error(`Error in ${key} query:`, err);
|
|
results[key] = 0;
|
|
} else {
|
|
results[key] = row.count;
|
|
}
|
|
|
|
completed++;
|
|
if (completed === queryKeys.length) {
|
|
res.json(results);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// 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', () => {
|
|
db.close((err) => {
|
|
if (err) {
|
|
console.error(err.message);
|
|
}
|
|
console.log('Database connection closed.');
|
|
process.exit(0);
|
|
});
|
|
}); |