demeter

Autonomous Hydroponic Intelligence

backend/node_server/controllers/cropController.js (4176B)


  1const CropStateSchema = require("../schema/cropSchema");
  2
  3// HELPERS
  4
  5/** Generates a fake sensor hardware ID */
  6function fakeSensorId(prefix) {
  7  const chars = "ABCDEF0123456789";
  8  const rand4 = () =>
  9    Array.from(
 10      { length: 4 },
 11      () => chars[Math.floor(Math.random() * chars.length)],
 12    ).join("");
 13  return `${prefix}-SEN-${rand4()}`;
 14}
 15
 16/** Auto cycle_duration_hours per crop type */
 17const CYCLE_HOURS = {
 18  lettuce: 1,
 19  basil: 1,
 20  tomato: 2,
 21  strawberry: 2,
 22};
 23
 24function cycleDurationForCrop(cropName) {
 25  if (!cropName) return 1;
 26  return CYCLE_HOURS[cropName.toLowerCase()] || 1;
 27}
 28
 29// CONTROLLERS
 30
 31const createCrop = async (req, res) => {
 32  try {
 33    const { crop_id, crop, stage, location, notes, image_url, ...rest } =
 34      req.body;
 35
 36    if (!crop_id) {
 37      return res.status(400).json({ error: "crop_id is required" });
 38    }
 39
 40    const existingCrop = await CropStateSchema.findOne({ crop_id });
 41    if (existingCrop) {
 42      return res
 43        .status(409)
 44        .json({ error: "Crop with this ID already exists" });
 45    }
 46
 47    const newCrop = new CropStateSchema({
 48      crop_id,
 49      crop,
 50      stage: stage || "seedling",
 51      sequence_number: 0,
 52      total_crop_lifetime_days: 0,
 53      planted_at: new Date(),
 54      last_updated: new Date(),
 55
 56      // Auto-set initial sensor arrays
 57      sensors: {
 58        pH: [6.0],
 59        EC: [1.5],
 60        temp: [24.0],
 61        humidity: [60.0],
 62      },
 63
 64      // Fallback to auto-generated fake sensor IDs
 65      sensor_ids: {
 66        ph_sensor: req.body.sensor_ids?.ph_sensor || fakeSensorId("PH"),
 67        ec_sensor: req.body.sensor_ids?.ec_sensor || fakeSensorId("EC"),
 68        temp_sensor: req.body.sensor_ids?.temp_sensor || fakeSensorId("TMP"),
 69        humidity_sensor:
 70          req.body.sensor_ids?.humidity_sensor || fakeSensorId("HUM"),
 71      },
 72
 73      // Auto-set cycle_duration_hours based on crop type
 74      cycle_duration_hours: cycleDurationForCrop(crop),
 75
 76      location: location || "",
 77      notes: notes || "",
 78      image_url: image_url || "",
 79
 80      ...rest,
 81    });
 82
 83    const savedCrop = await newCrop.save();
 84
 85    res.status(201).json({
 86      message: "Crop created successfully",
 87      data: savedCrop,
 88    });
 89  } catch (error) {
 90    res.status(500).json({ error: error.message });
 91  }
 92};
 93
 94const getAllCrops = async (req, res) => {
 95  console.log("getAllCrops endpoint called");
 96  try {
 97    const crops = await CropStateSchema.find();
 98    console.log(`Retrieved ${crops.length} crops from the database.`);
 99    res.status(200).json({
100      message: "Crops retrieved successfully",
101      data: crops,
102    });
103  } catch (error) {
104    res.status(500).json({ error: error.message });
105  } finally {
106    console.log("getAllCrops endpoint was called");
107  }
108};
109
110const getCropById = async (req, res) => {
111  try {
112    const { cropId } = req.params;
113    const crop = await CropStateSchema.findOne({ crop_id: cropId });
114    if (!crop) {
115      return res.status(404).json({ error: "Crop not found" });
116    }
117    res.status(200).json({ data: crop });
118  } catch (error) {
119    res.status(500).json({ error: error.message });
120  }
121};
122
123const updateCrop = async (req, res) => {
124  try {
125    const { cropId } = req.params;
126    const updates = { ...req.body, last_updated: new Date() };
127
128    const updated = await CropStateSchema.findOneAndUpdate(
129      { crop_id: cropId },
130      { $set: updates },
131      { new: true, runValidators: true },
132    );
133
134    if (!updated) {
135      return res.status(404).json({ error: "Crop not found" });
136    }
137    res.status(200).json({ message: "Crop updated", data: updated });
138  } catch (error) {
139    res.status(500).json({ error: error.message });
140  }
141};
142
143const deleteCrop = async (req, res) => {
144  try {
145    const { cropId } = req.params;
146    const deleted = await CropStateSchema.findOneAndDelete({ crop_id: cropId });
147    if (!deleted) {
148      return res.status(404).json({ error: "Crop not found" });
149    }
150    res.status(200).json({ message: "Crop deleted", data: deleted });
151  } catch (error) {
152    res.status(500).json({ error: error.message });
153  }
154};
155
156module.exports = {
157  createCrop,
158  getAllCrops,
159  getCropById,
160  updateCrop,
161  deleteCrop,
162};