From 60793e09bf3e587fbbdf4df1a9bcecd4be7f9740 Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 21:45:54 +0200
Subject: [PATCH 01/27] #63 added simple confirm dialog before plant deletion
---
frontend/public/js/detailseite_pflanze.mjs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/frontend/public/js/detailseite_pflanze.mjs b/frontend/public/js/detailseite_pflanze.mjs
index 47c5fb5..7851442 100644
--- a/frontend/public/js/detailseite_pflanze.mjs
+++ b/frontend/public/js/detailseite_pflanze.mjs
@@ -128,6 +128,11 @@ async function onButtonRepotPlantClick(plant) {
}
async function onButtonDeletePlantClick(plant_id) {
+ if(!confirm("Willst du die Pflanze wirklich löschen?"))
+ {
+ return;
+ }
+
try {
await backend.deletePlant(plant_id);
} catch (error) {
From 82706fbe0f800ffac4e6ea4aa45ce743e648a95d Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Fri, 5 Sep 2025 22:39:59 +0200
Subject: [PATCH 02/27] Updated Backend version and fixed typo
---
backend/package.json | 2 +-
backend/server.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/package.json b/backend/package.json
index 4525d30..1ca65c6 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "plant_manager",
- "version": "0.1.0",
+ "version": "1.1.0",
"description": "Backend for PlantManager with fileupload and sqlite",
"main": "server.js",
"scripts": {
diff --git a/backend/server.js b/backend/server.js
index a5f6333..df61ab9 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -73,7 +73,7 @@ try
// ====== BINDING ENDPOINTS ======
const TOPLEVELPATH = '/api';
let serviceRouter;
- console.log('Binding enpoints, top level Path at ' + TOPLEVELPATH);
+ console.log('Binding endpoints, top level Path at ' + TOPLEVELPATH);
serviceRouter = require('./services/plants.js');
app.use(TOPLEVELPATH, serviceRouter);
From f516ce40d065cdae50cb0b2a5d557f1d106bd8c3 Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Fri, 5 Sep 2025 22:41:00 +0200
Subject: [PATCH 03/27] #63 added new column to DB
---
backend/dbMigrationTool.js | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/backend/dbMigrationTool.js b/backend/dbMigrationTool.js
index 76b7995..66dce3c 100644
--- a/backend/dbMigrationTool.js
+++ b/backend/dbMigrationTool.js
@@ -4,7 +4,20 @@
* @returns {boolean} false if the DB is fine and true if it needs to be migrated
*/
module.exports.dbNeedsMigration = function(dbConnection) {
- // TODO implement this when necessary
+ // ============== Check requirement for v1.0.1 ==============
+ const columnExists = dbConnection.prepare(`
+ PRAGMA table_info(plants)
+ `).all().some(col => col.name === 'composted');
+
+ if(!columnExists)
+ {
+ return true;
+ }
+
+ // ============== Check requirement for v.. ==============
+ // ...
+
+ // All good
return false;
}
@@ -13,5 +26,21 @@ module.exports.dbNeedsMigration = function(dbConnection) {
* @param {*} dbConnection the connection to the DB
*/
module.exports.migrateDB = function(dbConnection) {
- // TODO implement this when necessary
+ // ============== Upgrade from v1.0.0 to v1.1.0 ==============
+ // Add 'composted' column to 'plants' table if it does not exist
+ const columnExists = dbConnection.prepare(`
+ PRAGMA table_info(plants)
+ `).all().some(col => col.name === 'composted');
+
+ if (!columnExists) {
+ dbConnection.prepare(`
+ ALTER TABLE plants ADD COLUMN composted DATE DEFAULT NULL
+ `).run();
+ console.log("Column 'composted' added to 'plants' table.");
+ } else {
+ console.log("Column 'composted' already exists in 'plants' table.");
+ }
+
+ // ============== Upgrade from v1.0.0 to v.. ==============
+ // ...
}
\ No newline at end of file
From 21dc832c08a84f8534f28e327145d9fb3c5476f4 Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Sat, 6 Sep 2025 00:08:38 +0200
Subject: [PATCH 04/27] #63 updated API
---
docs/API.md | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/docs/API.md b/docs/API.md
index 3425f61..bddc23f 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -110,7 +110,8 @@ Beispiel:
"watering_interval_calculated": 11,
"days_since_watering": 10,
"days_until_watering": 1,
- "repotted": "2023-03-14"
+ "repotted": "2023-03-14",
+ "composted": null
}
```
### Attribute
@@ -178,6 +179,12 @@ Beispiel:
- Durch Backend berechneter Wert
- Datum des letzten Umtopfen
- Wenn noch nie umgetopft Datum des hinzufügens
+- composted
+ - Typ: Text(Datum)
+ - Pflichtfeld bei: nie
+ - Nullbar: ja
+ - Datum des soften-löschens
+ - Wenn die Pflanze noch nicht soft-gelöscht wurde null
## Activities
- GET
From 406ec40b58f26f907e9a5aacd2cc15581ba264c6 Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Sat, 6 Sep 2025 16:15:19 +0200
Subject: [PATCH 05/27] Added type impots for dbConnection in the backend and
removed unused function
---
backend/dao/activitiesDao.js | 7 +++----
backend/dao/plantsDao.js | 11 +++++++----
backend/dao/templateDao.js | 7 +++----
backend/dbMigrationTool.js | 4 ++--
4 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/backend/dao/activitiesDao.js b/backend/dao/activitiesDao.js
index c63cee1..76e2740 100644
--- a/backend/dao/activitiesDao.js
+++ b/backend/dao/activitiesDao.js
@@ -4,14 +4,13 @@ const daoHelper = require('./daoHelper.js');
class activitiesDao {
+ /**
+ * @param {import('better-sqlite3').Database} dbConnection - The database connection
+ */
constructor(dbConnection) {
this._conn = dbConnection;
}
- getConnection() {
- return this._conn;
- }
-
loadById(id) {
var sql = 'SELECT * FROM activities WHERE id=? ORDER BY date DESC';
var statement = this._conn.prepare(sql);
diff --git a/backend/dao/plantsDao.js b/backend/dao/plantsDao.js
index b3009eb..fccbead 100644
--- a/backend/dao/plantsDao.js
+++ b/backend/dao/plantsDao.js
@@ -2,16 +2,19 @@
const helper = require('../helper.js');
const daoHelper = require('./daoHelper.js');
+
+/**
+ * Data Access Object for plants
+ */
class plantsDao {
+ /**
+ * @param {import('better-sqlite3').Database} dbConnection - The database connection
+ */
constructor(dbConnection) {
this._conn = dbConnection;
}
- getConnection() {
- return this._conn;
- }
-
loadById(plant_id) {
var sql = 'SELECT * FROM plants WHERE plant_id=?';
var statement = this._conn.prepare(sql);
diff --git a/backend/dao/templateDao.js b/backend/dao/templateDao.js
index 53ee64b..5024c8b 100644
--- a/backend/dao/templateDao.js
+++ b/backend/dao/templateDao.js
@@ -5,14 +5,13 @@ const helper = require('../helper.js');
class templateDao {
+ /**
+ * @param {import('better-sqlite3').Database} dbConnection - The database connection
+ */
constructor(dbConnection) {
this._conn = dbConnection;
}
- getConnection() {
- return this._conn;
- }
-
loadById(id) {
// Code goes here
diff --git a/backend/dbMigrationTool.js b/backend/dbMigrationTool.js
index 66dce3c..defa4c0 100644
--- a/backend/dbMigrationTool.js
+++ b/backend/dbMigrationTool.js
@@ -1,6 +1,6 @@
/**
* Checks if the DB is from an earlier version and needs to be upgraded
- * @param {*} dbConnection the connection to the DB
+ * @param {import('better-sqlite3').Database} dbConnection the connection to the DB
* @returns {boolean} false if the DB is fine and true if it needs to be migrated
*/
module.exports.dbNeedsMigration = function(dbConnection) {
@@ -23,7 +23,7 @@ module.exports.dbNeedsMigration = function(dbConnection) {
/**
* Migrates the DB to the current schema
- * @param {*} dbConnection the connection to the DB
+ * @param {import('better-sqlite3').Database} dbConnection the connection to the DB
*/
module.exports.migrateDB = function(dbConnection) {
// ============== Upgrade from v1.0.0 to v1.1.0 ==============
From 5d43e6e9b1f3388dc1a1da328160dd6c87ebf8be Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Sat, 6 Sep 2025 16:24:49 +0200
Subject: [PATCH 06/27] #63 updated API further
---
docs/API.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index bddc23f..792abc5 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -79,6 +79,11 @@ Alle HTTP Aufrufe sind englisch und kleingeschrieben!
- GET
- `…/api/plants/all`
- Liefert alle JSON Objekte vom Typ Plant
+ - Pflanzen die kompostiert wurden werden nicht berücksichtigt
+- GET
+ - `…/api/plants/composted`
+ - Liefert alle JSON Objekte vom Typ Plant
+ - Nur Pflanzen die kompostiert wurden werden berücksichtigt
- GET
- `…/api/plants/exists/[id]`
- Prüft nach, ob ein Objekt vom Typ Plant unter dieser [id] existiert
From e5b06c4f4128cb2d7396aea632ee5ea90cf617e9 Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Sat, 6 Sep 2025 16:35:24 +0200
Subject: [PATCH 07/27] #63 implemented API changes into DAO
---
backend/dao/plantsDao.js | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/backend/dao/plantsDao.js b/backend/dao/plantsDao.js
index fccbead..007d96a 100644
--- a/backend/dao/plantsDao.js
+++ b/backend/dao/plantsDao.js
@@ -27,8 +27,25 @@ class plantsDao {
return result;
}
+ /**
+ * Loads all plants from the DB
+ * Ignores plants that have been composted
+ * @returns {json[]}
+ */
loadAll() {
- var sql = 'SELECT * from plants order by name';
+ var sql = 'SELECT * from plants order by name WHERE composted IS NULL';
+ var statement = this._conn.prepare(sql);
+ var result = statement.all();
+ var arrayResult = daoHelper.guaranteeArray(result);
+ return arrayResult;
+ }
+
+ /**
+ * Loads all composted plants from the DB
+ * @returns {json[]}
+ */
+ loadAllComposted() {
+ var sql = 'SELECT * from plants order by name WHERE composted IS NOT NULL';
var statement = this._conn.prepare(sql);
var result = statement.all();
var arrayResult = daoHelper.guaranteeArray(result);
@@ -61,10 +78,10 @@ class plantsDao {
return this.loadById(result.lastInsertRowid);
}
- update(plant_id, name, species_name, image, watering_interval, watering_interval_offset) {
- var sql = 'UPDATE plants SET name=?, species_name=?, image=?, watering_interval=?, watering_interval_offset=? WHERE plant_id=?';
+ update(plant_id, name, species_name, image, watering_interval, watering_interval_offset, composted) {
+ var sql = 'UPDATE plants SET name=?, species_name=?, image=?, watering_interval=?, watering_interval_offset=?, composted=? WHERE plant_id=?';
var statement = this._conn.prepare(sql);
- var params = [name, species_name, image, watering_interval, watering_interval_offset, plant_id];
+ var params = [name, species_name, image, watering_interval, watering_interval_offset, composted, plant_id];
var result = statement.run(params);
if (result.changes != 1){
From b491163f622a232225c507997aa07b0ca13ca13b Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Sat, 6 Sep 2025 16:58:33 +0200
Subject: [PATCH 08/27] #63 implemented API changes into plants service
---
backend/services/plants.js | 33 ++++++++++++++++++++++++++++++---
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/backend/services/plants.js b/backend/services/plants.js
index 567e03c..5bf0686 100644
--- a/backend/services/plants.js
+++ b/backend/services/plants.js
@@ -104,6 +104,26 @@ serviceRouter.get('/plants/get/:id', function(request, response) {
}
});
+serviceRouter.get('/plants/composted', function(request, response) {
+ console.log('Service plants: Client requested all records');
+
+ const plantDaoInstance = new plantsDao(request.app.locals.dbConnection);
+ const activitiesDaoInstance = new activitiesDao(request.app.locals.dbConnection);
+ try {
+ var plantArr = plantDaoInstance.loadAllComposted();
+ // foreach Schleife über alle plant JSON, diese werden dabei erweitert
+ plantArr.forEach(plant => {
+ extendPlantJSON(plant,activitiesDaoInstance);
+ });
+
+ console.log('Service plants: Records loaded, count= ' + plantArr.length);
+ response.status(200).json(plantArr);
+ } catch (ex) {
+ console.error('Service plants: Error loading all records. Exception occured: ' + ex.message);
+ response.status(400).json({ 'fehler': true, 'nachricht': ex.message });
+ }
+});
+
serviceRouter.get('/plants/all', function(request, response) {
console.log('Service plants: Client requested all records');
@@ -185,6 +205,7 @@ serviceRouter.post('/plants', function(request, response) {
serviceRouter.put('/plants', function(request, response) {
console.log('Service plants: Client requested update of existing plant');
+ // TODO Replace this madness with a validation Framework like express validator
const plantDaoInstance = new plantsDao(request.app.locals.dbConnection);
var errorMsgs=[];
if (helper.isUndefined(request.body.plant_id)) {
@@ -194,29 +215,35 @@ serviceRouter.put('/plants', function(request, response) {
} else if (request.body.plant_id <= 0) {
errorMsgs.push('plant_id has to be a bigger number than 0');
}
+
if (helper.isUndefined(request.body.name)) {
errorMsgs.push('name missing');
- }
+ }
+
if (helper.isUndefined(request.body.species_name)) {
errorMsgs.push('species_name missing');
}
+
if (helper.isUndefined(request.body.watering_interval)) {
errorMsgs.push('watering_interval missing');
} else if (!helper.isNumeric(request.body.watering_interval)) {
errorMsgs.push('watering_interval has to be a number');
} else if (request.body.watering_interval <= 0) {
- errorMsgs.push('watering_interval has to be a bigger number than 0');
+ errorMsgs.push('watering_interval has to be a number bigger than 0');
}
+
if (helper.isUndefined(request.body.watering_interval_offset)) {
errorMsgs.push('watering_interval_offset missing');
} else if (!helper.isNumeric(request.body.watering_interval_offset)) {
errorMsgs.push('watering_interval has to be a number');
}
+
if (errorMsgs.length > 0) {
console.log('Service plants: Update not possible, data missing or invalid');
response.status(400).json({ 'fehler': true, 'nachricht': 'Function not possible. Missing or invalid data: ' + helper.concatArray(errorMsgs) });
return;
}
+
try {
// check if the plant even exists
if(!plantDaoInstance.exists(request.body.plant_id))
@@ -230,7 +257,7 @@ serviceRouter.put('/plants', function(request, response) {
let image = plantDaoInstance.loadById(request.body.plant_id).image;
// update the plant
- var obj = plantDaoInstance.update(request.body.plant_id,request.body.name, request.body.species_name,image,request.body.watering_interval,request.body.watering_interval_offset);
+ var obj = plantDaoInstance.update(request.body.plant_id,request.body.name, request.body.species_name,image,request.body.watering_interval,request.body.watering_interval_offset,request.body.composted);
console.log('Service plants: Record updated, plant_id=' + request.body.plant_id);
response.status(200).json(obj);
} catch (ex) {
From 67aab81dbb34510a00a44740fa6b24c57a9657d9 Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Sat, 6 Sep 2025 17:49:41 +0200
Subject: [PATCH 09/27] #63 tested backend changes and fixed SQL query
---
backend/dao/plantsDao.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/dao/plantsDao.js b/backend/dao/plantsDao.js
index 007d96a..6e7692a 100644
--- a/backend/dao/plantsDao.js
+++ b/backend/dao/plantsDao.js
@@ -33,7 +33,7 @@ class plantsDao {
* @returns {json[]}
*/
loadAll() {
- var sql = 'SELECT * from plants order by name WHERE composted IS NULL';
+ var sql = 'SELECT * FROM plants WHERE composted IS NULL ORDER BY name';
var statement = this._conn.prepare(sql);
var result = statement.all();
var arrayResult = daoHelper.guaranteeArray(result);
@@ -45,7 +45,7 @@ class plantsDao {
* @returns {json[]}
*/
loadAllComposted() {
- var sql = 'SELECT * from plants order by name WHERE composted IS NOT NULL';
+ var sql = 'SELECT * FROM plants WHERE composted IS NOT NULL ORDER BY name';
var statement = this._conn.prepare(sql);
var result = statement.all();
var arrayResult = daoHelper.guaranteeArray(result);
From 23340006e9847d441c81fcb1c08914ca64acb29d Mon Sep 17 00:00:00 2001
From: CoderTobi <77673526+CoderTobi@users.noreply.github.com>
Date: Sat, 6 Sep 2025 23:02:57 +0200
Subject: [PATCH 10/27] changed activity functions to only require plant_id
---
frontend/public/js/detailseite_pflanze.mjs | 4 ++--
frontend/public/js/index.mjs | 2 +-
frontend/public/js/meine_pflanzen.mjs | 4 ++--
frontend/public/mjs/backend_api.mjs | 20 ++++++++++----------
4 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/frontend/public/js/detailseite_pflanze.mjs b/frontend/public/js/detailseite_pflanze.mjs
index 7851442..045db6a 100644
--- a/frontend/public/js/detailseite_pflanze.mjs
+++ b/frontend/public/js/detailseite_pflanze.mjs
@@ -101,7 +101,7 @@ function createActivityCard(type, date, days_since) {
async function onButtonWaterPlantClick(plant) {
// call Backend
try{
- await backend.waterPlant(plant);
+ await backend.waterPlant(plant.plant_id);
}
catch(e)
{
@@ -115,7 +115,7 @@ async function onButtonWaterPlantClick(plant) {
async function onButtonRepotPlantClick(plant) {
// call Backend
try{
- await backend.repotPlant(plant);
+ await backend.repotPlant(plant.plant_id);
}
catch(e)
{
diff --git a/frontend/public/js/index.mjs b/frontend/public/js/index.mjs
index fc090d6..975cf0c 100644
--- a/frontend/public/js/index.mjs
+++ b/frontend/public/js/index.mjs
@@ -165,7 +165,7 @@ async function buttonWaterClick(plant)
{
// call Backend
try{
- await backend.waterPlant(plant);
+ await backend.waterPlant(plant.plant_id);
}
catch(e)
{
diff --git a/frontend/public/js/meine_pflanzen.mjs b/frontend/public/js/meine_pflanzen.mjs
index e976d34..464b77c 100644
--- a/frontend/public/js/meine_pflanzen.mjs
+++ b/frontend/public/js/meine_pflanzen.mjs
@@ -97,7 +97,7 @@ function createPlantCard(plant) {
`);
cardFooter.append(buttonWater);
buttonWater.on("click", () => {
- // needed to be wraped in this anonymus function
+ // needed to be wrapped in this anonymous function
buttonWaterClick(plant);
});
let buttonDetails = $(`