Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
60793e0
#63 added simple confirm dialog before plant deletion
CoderTobi Sep 2, 2025
82706fb
Updated Backend version and fixed typo
CoderTobi Sep 5, 2025
f516ce4
#63 added new column to DB
CoderTobi Sep 5, 2025
21dc832
#63 updated API
CoderTobi Sep 5, 2025
406ec40
Added type impots for dbConnection in the backend and removed unused …
CoderTobi Sep 6, 2025
5d43e6e
#63 updated API further
CoderTobi Sep 6, 2025
e5b06c4
#63 implemented API changes into DAO
CoderTobi Sep 6, 2025
b491163
#63 implemented API changes into plants service
CoderTobi Sep 6, 2025
67aab81
#63 tested backend changes and fixed SQL query
CoderTobi Sep 6, 2025
2334000
changed activity functions to only require plant_id
CoderTobi Sep 6, 2025
ea95f2e
#63 implemented compostPlant and necessary helper function
CoderTobi Sep 6, 2025
b0f55dd
moved css into folder and updated template head in frontend_static
CoderTobi Sep 6, 2025
3a4158c
#63 Changed delete button into compost button
CoderTobi Sep 6, 2025
41c8c59
#63 Compost page design
CoderTobi Sep 6, 2025
dda3921
Fixed Typo on details page
CoderTobi Sep 7, 2025
59be8bc
#63 work on compost page implementation
CoderTobi Sep 7, 2025
37b6cc7
Fixed sqlDate conversion function
CoderTobi Sep 7, 2025
036a132
#63 work on compost page implementation
CoderTobi Sep 7, 2025
5dabe0c
#63 renamed delete button id and disabled button when the plant is co…
CoderTobi Sep 7, 2025
d23719d
#63 work on compost page implementation
CoderTobi Sep 7, 2025
d53689b
#63 added restore function
CoderTobi Sep 7, 2025
78a8a53
#63 compost page finishing touch
CoderTobi Sep 7, 2025
c62c3d1
Updated alerts system to also display icons
CoderTobi Sep 8, 2025
fef626a
created test page and moved all development related pages inside a de…
CoderTobi Sep 8, 2025
602e2ac
#63 added alert to indicate that a plant has been composted
CoderTobi Sep 8, 2025
92278b5
#63 Added compost page to navbar
CoderTobi Sep 8, 2025
00140a9
#63 changed frontend version to v1.1.0
CoderTobi Sep 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions backend/dao/activitiesDao.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
36 changes: 28 additions & 8 deletions backend/dao/plantsDao.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -24,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 WHERE composted IS NULL ORDER BY name';
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 WHERE composted IS NOT NULL ORDER BY name';
var statement = this._conn.prepare(sql);
var result = statement.all();
var arrayResult = daoHelper.guaranteeArray(result);
Expand Down Expand Up @@ -58,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){
Expand Down
7 changes: 3 additions & 4 deletions backend/dao/templateDao.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 33 additions & 4 deletions backend/dbMigrationTool.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,46 @@
/**
* 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) {
// 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;
}

/**
* 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) {
// 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.. ==============
// ...
}
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
33 changes: 30 additions & 3 deletions backend/services/plants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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)) {
Expand All @@ -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))
Expand All @@ -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) {
Expand Down
14 changes: 13 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -110,7 +115,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
Expand Down Expand Up @@ -178,6 +184,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
Expand Down
3 changes: 2 additions & 1 deletion frontend/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ npm-debug.log
Dockerfile
.dockerignore
jsconfig.json
eslint.config.mjs
eslint.config.mjs
*/dev/*
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "plant_manager_frontend",
"version": "1.0.0",
"version": "1.1.0",
"description": "Frontend for PlantManager",
"main": "client.js",
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions frontend/public/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ main {
background-color: white !important;
}

.list-group-item {
background-color: white !important;
}

.text-water {
color: var(--qd-color-water);
}
Expand Down
9 changes: 6 additions & 3 deletions frontend/public/detailseite_pflanze.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
<li class="nav-item">
<a class="nav-link" href="meine_pflanzen.html">Meine Pflanzen</a>
</li>
<li class="nav-item">
<a class="nav-link" href="kompostierte_pflanzen.html">Kompost</a>
</li>
</ul>
</div>

Expand All @@ -60,15 +63,15 @@
<div class="col-12 col-md-7 col-lg-8">
<div class="card-body h-100 d-flex flex-column">

<button class="btn btn-delete-plant position-absolute top-0 end-0 p-2 m-2" id="delete-button">
<i class="bi bi-trash"></i>
<button class="btn btn-delete-plant position-absolute top-0 end-0 p-2 m-2" id="compost-button">
<i class="bi bi-recycle"></i>
</button>

<h5 class="card-title fs-3" id="plant-name">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
Lade Planze
Lade Pflanze
</h5>

<p class="card-subtitle mb-2 text-muted fs-5" id="species-name">-</p>
Expand Down
Loading