Merge laatste worktree in master

Neemt de laatste PantryHub-implementatie over in de hoofdbranch.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-08-07 20:46:00 +02:00
21 changed files with 345 additions and 1128 deletions
+19 -16
View File
@@ -3,51 +3,54 @@ require("../services/ProductService");
const db = require("../config/database");
class ProductController {
index(req, res) {
const products =
ProductService.getProducts();
const categories = db.prepare(`
SELECT * FROM categories ORDER BY name
`).all();
res.locals.title = "Producten";
const categories = db.prepare(
"SELECT * FROM categories ORDER BY name"
).all();
res.render(
"products/index",
{
title:"Producten",
products,
categories,
user: req.user
categories
}
);
}
create(req, res) {
const name = (req.body.name || "").trim();
const categoryId = req.body.category_id
? Number.parseInt(req.body.category_id, 10)
: null;
const { name, category_id } = req.body;
if (!name || name.trim().length === 0) {
req.flash("error", "Geef een productnaam op");
if (!name || (categoryId !== null && !Number.isInteger(categoryId))) {
req.flash("error", "Geef een geldige productnaam en categorie op.");
return res.redirect("/products");
}
ProductService.createProduct({
name: name.trim(),
category_id: category_id || null
name,
category_id: categoryId
});
req.flash("success", "Product toegevoegd");
req.flash("success", "Product toegevoegd.");
res.redirect("/products");
}
}
module.exports =
new ProductController();
+36 -144
View File
@@ -1,179 +1,71 @@
const ShoppingListRepository = require("../repositories/ShoppingListRepository");
const ProductRepository = require("../repositories/ProductRepository");
const ShoppingListRepository = require("../repositories/ShoppingListRepository");
class ShoppingListController {
index(req, res) {
const lists = ShoppingListRepository.getByHousehold(
req.user.household_id
);
const lists = ShoppingListRepository.getByHousehold(req.user.household_id);
res.locals.title = "Boodschappenlijsten";
res.render("lists/index", {
lists,
user: req.user
});
res.render("lists/index", { lists, user: req.user });
}
create(req, res) {
const { name } = req.body;
if (!name || name.trim().length === 0) {
req.flash("error", "Geef een naam op");
const name = (req.body.name || "").trim();
if (!name) {
req.flash("error", "Geef een lijstnaam op");
return res.redirect("/lists");
}
// Check if user has a household
if (!req.user || !req.user.household_id) {
req.flash("error", "Je bent niet aan een huishouden gekoppeld");
return res.redirect("/lists");
}
ShoppingListRepository.create({
household_id: req.user.household_id,
name: name.trim()
});
ShoppingListRepository.create(req.user.household_id, name);
req.flash("success", "Boodschappenlijst aangemaakt");
res.redirect("/lists");
}
show(req, res) {
const { id } = req.params;
const list = ShoppingListRepository.getById(id);
if (!list) {
return res.status(404).render("errors/404");
}
if (list.household_id !== req.user.household_id) {
return res.status(403).render("errors/404");
}
const items = ShoppingListRepository.getItems(id);
const list = ShoppingListRepository.findById(req.params.id, req.user.household_id);
if (!list) return res.status(404).render("errors/404");
res.locals.title = list.name;
res.render("lists/show", {
list,
items,
items: ShoppingListRepository.getItems(list.id),
products: ProductRepository.getAll(),
user: req.user
});
}
addItem(req, res) {
const { id } = req.params;
const { product_id, amount } = req.body;
const list = ShoppingListRepository.getById(id);
if (!list) {
return res.status(404).json({ error: "Lijst niet gevonden" });
const list = ShoppingListRepository.findById(req.params.id, req.user.household_id);
const productId = Number.parseInt(req.body.product_id, 10);
const amount = Number.parseFloat(req.body.amount || "1");
const product = Number.isInteger(productId) ? ProductRepository.findById(productId) : null;
if (!list || !product || !Number.isFinite(amount) || amount <= 0) {
req.flash("error", "Ongeldig product of ongeldige hoeveelheid");
return res.redirect(`/lists/${req.params.id}`);
}
if (list.household_id !== req.user.household_id) {
return res.status(403).json({ error: "Niet gemachtigd" });
}
const product = ProductRepository.findById(product_id);
if (!product) {
return res.status(404).json({ error: "Product niet gevonden" });
}
const quantity = Number(amount);
if (!Number.isFinite(quantity) || quantity <= 0 || quantity > 999) {
req.flash("error", "Vul een geldige hoeveelheid in");
return res.redirect(`/lists/${id}`);
}
const item = ShoppingListRepository.addItem({
list_id: id,
product_id,
amount: quantity
});
req.app.locals.io.to(`household:${req.user.household_id}`).emit("item:added", {
listId: Number(id)
});
req.flash("success", "Product toegevoegd");
if (req.xhr || req.headers.accept?.includes("application/json")) {
return res.json({ success: true, item });
}
res.redirect(`/lists/${id}`);
ShoppingListRepository.addItem(list.id, product.id, amount);
res.redirect(`/lists/${list.id}`);
}
toggleItem(req, res) {
const { id, itemId } = req.params;
const list = ShoppingListRepository.getById(id);
if (!list || list.household_id !== req.user.household_id) {
return res.status(403).json({ error: "Niet gemachtigd" });
}
if (!ShoppingListRepository.findItem(id, itemId)) {
return res.status(404).json({ error: "Item niet gevonden" });
}
ShoppingListRepository.toggleItem(itemId);
req.app.locals.io.to(`household:${req.user.household_id}`).emit("item:toggled", {
listId: Number(id), itemId: Number(itemId)
});
if (req.xhr || req.headers.accept?.includes("application/json")) {
return res.json({ success: true });
}
res.redirect(`/lists/${id}`);
const list = ShoppingListRepository.findById(req.params.id, req.user.household_id);
if (!list) return res.status(404).render("errors/404");
ShoppingListRepository.toggleItem(list.id, req.params.itemId);
this.broadcastUpdate(req, list.id);
res.redirect(`/lists/${list.id}`);
}
deleteItem(req, res) {
const { id, itemId } = req.params;
const list = ShoppingListRepository.getById(id);
if (!list || list.household_id !== req.user.household_id) {
return res.status(403).json({ error: "Niet gemachtigd" });
}
if (!ShoppingListRepository.findItem(id, itemId)) {
return res.status(404).json({ error: "Item niet gevonden" });
}
ShoppingListRepository.deleteItem(itemId);
req.app.locals.io.to(`household:${req.user.household_id}`).emit("item:removed", {
listId: Number(id), itemId: Number(itemId)
});
req.flash("success", "Product verwijderd");
if (req.method === "DELETE" || req.xhr || req.headers.accept?.includes("application/json")) {
return res.json({ success: true });
}
res.redirect(`/lists/${id}`);
const list = ShoppingListRepository.findById(req.params.id, req.user.household_id);
if (!list) return res.status(404).json({ error: "Lijst niet gevonden" });
const result = ShoppingListRepository.deleteItem(list.id, req.params.itemId);
if (result.changes === 0) return res.status(404).json({ error: "Product niet gevonden" });
this.broadcastUpdate(req, list.id);
res.json({ success: true });
}
broadcastUpdate(req, listId) {
if (req.app.locals.io) {
req.app.locals.io.to(`list:${listId}`).emit("list:updated", { listId });
}
}
}
module.exports = new ShoppingListController();