diff --git a/controllers/CategoryController.js b/controllers/CategoryController.js index b9bd5db..0a03896 100644 --- a/controllers/CategoryController.js +++ b/controllers/CategoryController.js @@ -8,7 +8,7 @@ class CategoryController { res.render("categories/index", { title: "Categorieën", - categories: CategoryService.getCategories() + categories: CategoryService.getCategories(req.user.household_id) }); } @@ -17,7 +17,7 @@ class CategoryController { editForm(req, res) { const categoryId = Number.parseInt(req.params.id, 10); const category = Number.isInteger(categoryId) - ? CategoryService.getCategory(categoryId) + ? CategoryService.getCategory(categoryId, req.user.household_id) : null; if (!category) { @@ -41,7 +41,7 @@ class CategoryController { return res.redirect("/categories"); } - CategoryService.createCategory({ name, icon }); + CategoryService.createCategory({ name, icon, household_id: req.user.household_id }); req.flash("success", "Categorie toegevoegd."); res.redirect("/categories"); } @@ -57,7 +57,7 @@ class CategoryController { return res.redirect("/categories"); } - const result = CategoryService.updateCategory(categoryId, { name, icon }); + const result = CategoryService.updateCategory(categoryId, req.user.household_id, { name, icon }); if (result.changes === 0) { req.flash("error", "Categorie niet gevonden."); @@ -77,7 +77,7 @@ class CategoryController { return res.redirect("/categories"); } - if (CategoryService.categoryHasProducts(categoryId)) { + if (CategoryService.categoryHasProducts(categoryId, req.user.household_id)) { req.flash( "error", "Deze categorie is nog aan producten gekoppeld en kan niet worden verwijderd." @@ -85,7 +85,7 @@ class CategoryController { return res.redirect("/categories"); } - const result = CategoryService.deleteCategory(categoryId); + const result = CategoryService.deleteCategory(categoryId, req.user.household_id); if (result.changes === 0) { req.flash("error", "Categorie niet gevonden."); diff --git a/controllers/MenuController.js b/controllers/MenuController.js index 4d8af53..b709fc6 100644 --- a/controllers/MenuController.js +++ b/controllers/MenuController.js @@ -25,7 +25,7 @@ class MenuController { res.render("menu/preview", { title: "Weekmenu controleren", menu, - newProducts: MenuService.getNewProducts(menu), + newProducts: MenuService.getNewProducts(menu, req.user.household_id), listName: "Weekboodschappen" }); } catch (error) { diff --git a/controllers/ProductController.js b/controllers/ProductController.js index e9c2d3c..cab9126 100644 --- a/controllers/ProductController.js +++ b/controllers/ProductController.js @@ -1,7 +1,8 @@ const ProductService = require("../services/ProductService"); -const db = require("../config/database"); +const CategoryService = require("../services/CategoryService"); + const parseCsv = require("../utils/parseCsv"); class ProductController { @@ -10,11 +11,9 @@ class ProductController { index(req, res) { const products = - ProductService.getProducts(); + ProductService.getProducts(req.user.household_id); - const categories = db.prepare( - "SELECT * FROM categories ORDER BY name" - ).all(); + const categories = CategoryService.getCategories(req.user.household_id); res.render( "products/index", @@ -39,9 +38,15 @@ class ProductController { return res.redirect("/products"); } + if (categoryId !== null && !CategoryService.getCategory(categoryId, req.user.household_id)) { + req.flash("error", "De gekozen categorie bestaat niet."); + return res.redirect("/products"); + } + ProductService.createProduct({ name, - category_id: categoryId + category_id: categoryId, + household_id: req.user.household_id }); req.flash("success", "Product toegevoegd."); @@ -80,7 +85,7 @@ class ProductController { return res.redirect("/products"); } - const categories = db.prepare("SELECT id, name FROM categories").all(); + const categories = CategoryService.getCategories(req.user.household_id); const categoryIds = new Map( categories.map(category => [category.name.trim().toLowerCase(), category.id]) ); @@ -105,7 +110,8 @@ class ProductController { products.push({ name, category_id: categoryId, - barcode: barcodeIndex === -1 ? "" : (row[barcodeIndex] || "").trim() + barcode: barcodeIndex === -1 ? "" : (row[barcodeIndex] || "").trim(), + household_id: req.user.household_id }); }); @@ -123,7 +129,7 @@ class ProductController { editForm(req, res) { const productId = Number.parseInt(req.params.id, 10); const product = Number.isInteger(productId) - ? ProductService.getProduct(productId) + ? ProductService.getProduct(productId, req.user.household_id) : null; if (!product) { @@ -131,9 +137,7 @@ class ProductController { return res.redirect("/products"); } - const categories = db.prepare( - "SELECT * FROM categories ORDER BY name" - ).all(); + const categories = CategoryService.getCategories(req.user.household_id); res.render("products/edit", { title: "Product wijzigen", @@ -156,7 +160,12 @@ class ProductController { return res.redirect("/products"); } - const result = ProductService.updateProduct(productId, { + if (categoryId !== null && !CategoryService.getCategory(categoryId, req.user.household_id)) { + req.flash("error", "De gekozen categorie bestaat niet."); + return res.redirect(`/products/${productId}/edit`); + } + + const result = ProductService.updateProduct(productId, req.user.household_id, { name, category_id: categoryId }); @@ -179,7 +188,7 @@ class ProductController { return res.redirect("/products"); } - if (ProductService.productHasListItems(productId)) { + if (ProductService.productHasListItems(productId, req.user.household_id)) { req.flash( "error", "Dit product staat nog op een boodschappenlijst en kan niet worden verwijderd." @@ -187,7 +196,7 @@ class ProductController { return res.redirect(`/products/${productId}/edit`); } - const result = ProductService.deleteProduct(productId); + const result = ProductService.deleteProduct(productId, req.user.household_id); if (result.changes === 0) { req.flash("error", "Product niet gevonden."); diff --git a/controllers/ShoppingListController.js b/controllers/ShoppingListController.js index eff516d..5fdc169 100644 --- a/controllers/ShoppingListController.js +++ b/controllers/ShoppingListController.js @@ -25,8 +25,8 @@ class ShoppingListController { res.locals.title = list.name; res.render("lists/show", { list, - items: ShoppingListRepository.getItems(list.id), - products: ProductRepository.getAll(), + items: ShoppingListRepository.getItems(list.id, req.user.household_id), + products: ProductRepository.getAll(req.user.household_id), user: req.user }); } @@ -35,7 +35,9 @@ class ShoppingListController { 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; + const product = Number.isInteger(productId) + ? ProductRepository.findById(productId, req.user.household_id) + : null; if (!list || !product || !Number.isFinite(amount) || amount <= 0) { req.flash("error", "Ongeldig product of ongeldige hoeveelheid"); return res.redirect(`/lists/${req.params.id}`); diff --git a/database.sqlite-shm b/database.sqlite-shm index f2db13e..acb1895 100644 Binary files a/database.sqlite-shm and b/database.sqlite-shm differ diff --git a/database.sqlite-wal b/database.sqlite-wal index 94a2c09..2074094 100644 Binary files a/database.sqlite-wal and b/database.sqlite-wal differ diff --git a/database/migrate.js b/database/migrate.js index 694c3a1..239ab67 100644 --- a/database/migrate.js +++ b/database/migrate.js @@ -210,5 +210,23 @@ CREATE TABLE IF NOT EXISTS product_stores ( `); +const addColumnIfMissing = (table, column, definition) => { + const columns = db.prepare(`PRAGMA table_info(${table})`).all(); + if (!columns.some(existing => existing.name === column)) { + db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`); + } +}; + +addColumnIfMissing("categories", "household_id", "INTEGER REFERENCES households(id)"); +addColumnIfMissing("products", "household_id", "INTEGER REFERENCES households(id)"); + +const defaultHousehold = db.prepare("SELECT id FROM households ORDER BY id LIMIT 1").get(); +if (defaultHousehold) { + db.prepare("UPDATE categories SET household_id = ? WHERE household_id IS NULL") + .run(defaultHousehold.id); + db.prepare("UPDATE products SET household_id = ? WHERE household_id IS NULL") + .run(defaultHousehold.id); +} + console.log("Database migratie voltooid."); \ No newline at end of file diff --git a/database/seed.js b/database/seed.js index bc1115b..b61b8b8 100644 --- a/database/seed.js +++ b/database/seed.js @@ -19,24 +19,33 @@ const products = [ ["WC papier", "Huishouden"] ]; +const household = db.prepare( + "SELECT id FROM households ORDER BY id LIMIT 1" +).get(); + +if (!household) { + console.log("Seed overgeslagen: er is nog geen huishouden."); + process.exit(0); +} + const insertCategory = db.prepare(` - INSERT OR IGNORE INTO categories (name, icon) VALUES (?, ?) + INSERT OR IGNORE INTO categories (name, icon, household_id) VALUES (?, ?, ?) `); const insertProduct = db.prepare(` - INSERT OR IGNORE INTO products (name, category_id) VALUES (?, ?) + INSERT OR IGNORE INTO products (name, category_id, household_id) VALUES (?, ?, ?) `); db.transaction(() => { for (const category of categories) { - insertCategory.run(category); + insertCategory.run(category[0], category[1], household.id); } for (const [name, categoryName] of products) { const category = db.prepare( - "SELECT id FROM categories WHERE name = ?" - ).get(categoryName); - insertProduct.run(name, category.id); + "SELECT id FROM categories WHERE name = ? AND household_id = ?" + ).get(categoryName, household.id); + insertProduct.run(name, category.id, household.id); } })(); diff --git a/repositories/CategoryRepository.js b/repositories/CategoryRepository.js index 981f7e9..87e4d9e 100644 --- a/repositories/CategoryRepository.js +++ b/repositories/CategoryRepository.js @@ -4,7 +4,7 @@ const db = require("../config/database"); class CategoryRepository { - getAll() { + getAll(householdId) { return db.prepare(` @@ -13,23 +13,26 @@ class CategoryRepository { FROM categories LEFT JOIN products ON products.category_id = categories.id + AND products.household_id = categories.household_id + + WHERE categories.household_id = ? GROUP BY categories.id ORDER BY categories.name - `).all(); + `).all(householdId); } - findById(id) { + findById(id, householdId) { return db.prepare(` - SELECT * FROM categories WHERE id = ? + SELECT * FROM categories WHERE id = ? AND household_id = ? - `).get(id); + `).get(id, householdId); } @@ -38,16 +41,16 @@ class CategoryRepository { return db.prepare(` - INSERT INTO categories (name, icon) + INSERT INTO categories (name, icon, household_id) - VALUES (?, ?) + VALUES (?, ?, ?) - `).run(data.name, data.icon || null); + `).run(data.name, data.icon || null, data.household_id); } - update(id, data) { + update(id, householdId, data) { return db.prepare(` @@ -55,31 +58,31 @@ class CategoryRepository { SET name = ?, icon = ? - WHERE id = ? + WHERE id = ? AND household_id = ? - `).run(data.name, data.icon || null, id); + `).run(data.name, data.icon || null, id, householdId); } - hasProducts(id) { + hasProducts(id, householdId) { return db.prepare(` - SELECT 1 FROM products WHERE category_id = ? LIMIT 1 + SELECT 1 FROM products WHERE category_id = ? AND household_id = ? LIMIT 1 - `).get(id) !== undefined; + `).get(id, householdId) !== undefined; } - delete(id) { + delete(id, householdId) { return db.prepare(` - DELETE FROM categories WHERE id = ? + DELETE FROM categories WHERE id = ? AND household_id = ? - `).run(id); + `).run(id, householdId); } diff --git a/repositories/ProductRepository.js b/repositories/ProductRepository.js index 69cd21a..d7234d0 100644 --- a/repositories/ProductRepository.js +++ b/repositories/ProductRepository.js @@ -4,7 +4,7 @@ const db = require("../config/database"); class ProductRepository { - getAll() { + getAll(householdId) { return db.prepare(` @@ -24,16 +24,18 @@ class ProductRepository { ON categories.id = products.category_id + WHERE products.household_id = ? + ORDER BY products.name - `).all(); + `).all(householdId); } - findById(id) { + findById(id, householdId) { return db.prepare(` @@ -41,9 +43,9 @@ class ProductRepository { FROM products - WHERE id = ? + WHERE id = ? AND household_id = ? - `).get(id); + `).get(id, householdId); } @@ -59,10 +61,11 @@ class ProductRepository { ( name, category_id, - barcode + barcode, + household_id ) - VALUES (?, ?, ?) + VALUES (?, ?, ?, ?) `).run( @@ -70,7 +73,8 @@ class ProductRepository { data.category_id || null, - data.barcode || null + data.barcode || null, + data.household_id ); @@ -81,9 +85,9 @@ class ProductRepository { createMany(products) { const insert = db.prepare(` - INSERT INTO products (name, category_id, barcode) + INSERT INTO products (name, category_id, barcode, household_id) - VALUES (?, ?, ?) + VALUES (?, ?, ?, ?) `); @@ -92,7 +96,8 @@ class ProductRepository { insert.run( product.name, product.category_id || null, - product.barcode || null + product.barcode || null, + product.household_id ); } }); @@ -102,7 +107,7 @@ class ProductRepository { } - update(id, data) { + update(id, householdId, data) { return db.prepare(` @@ -110,7 +115,7 @@ class ProductRepository { SET name = ?, category_id = ? - WHERE id = ? + WHERE id = ? AND household_id = ? `).run( @@ -118,14 +123,15 @@ class ProductRepository { data.category_id || null, - id + id, + householdId ); } - hasListItems(id) { + hasListItems(id, householdId) { return db.prepare(` @@ -133,24 +139,28 @@ class ProductRepository { FROM shopping_list_items - WHERE product_id = ? + WHERE product_id = ? AND EXISTS ( + SELECT 1 FROM shopping_lists + WHERE shopping_lists.id = shopping_list_items.list_id + AND shopping_lists.household_id = ? + ) LIMIT 1 - `).get(id) !== undefined; + `).get(id, householdId) !== undefined; } - delete(id) { + delete(id, householdId) { return db.prepare(` DELETE FROM products - WHERE id = ? + WHERE id = ? AND household_id = ? - `).run(id); + `).run(id, householdId); } diff --git a/repositories/ShoppingListRepository.js b/repositories/ShoppingListRepository.js index 0220924..855b8cd 100644 --- a/repositories/ShoppingListRepository.js +++ b/repositories/ShoppingListRepository.js @@ -22,18 +22,19 @@ class ShoppingListRepository { `).get(id, householdId); } - getItems(listId) { + getItems(listId, householdId) { return db.prepare(` SELECT shopping_list_items.*, products.name AS product_name, categories.name AS category_name, categories.icon AS category_icon FROM shopping_list_items JOIN products ON products.id = shopping_list_items.product_id LEFT JOIN categories ON categories.id = products.category_id - WHERE shopping_list_items.list_id = ? + WHERE shopping_list_items.list_id = ? + AND products.household_id = ? ORDER BY shopping_list_items.checked ASC, shopping_list_items.sort_order ASC, shopping_list_items.created_at ASC - `).all(listId); + `).all(listId, householdId); } create(householdId, name) { diff --git a/services/CategoryService.js b/services/CategoryService.js index 85b7708..016b628 100644 --- a/services/CategoryService.js +++ b/services/CategoryService.js @@ -4,16 +4,16 @@ const CategoryRepository = require("../repositories/CategoryRepository"); class CategoryService { - getCategories() { + getCategories(householdId) { - return CategoryRepository.getAll(); + return CategoryRepository.getAll(householdId); } - getCategory(id) { + getCategory(id, householdId) { - return CategoryRepository.findById(id); + return CategoryRepository.findById(id, householdId); } @@ -25,23 +25,23 @@ class CategoryService { } - updateCategory(id, data) { + updateCategory(id, householdId, data) { - return CategoryRepository.update(id, data); + return CategoryRepository.update(id, householdId, data); } - categoryHasProducts(id) { + categoryHasProducts(id, householdId) { - return CategoryRepository.hasProducts(id); + return CategoryRepository.hasProducts(id, householdId); } - deleteCategory(id) { + deleteCategory(id, householdId) { - return CategoryRepository.delete(id); + return CategoryRepository.delete(id, householdId); } diff --git a/services/MenuService.js b/services/MenuService.js index c95bb16..57d16e6 100644 --- a/services/MenuService.js +++ b/services/MenuService.js @@ -82,9 +82,9 @@ class MenuService { } - getNewProducts(menu) { + getNewProducts(menu, householdId) { const existingNames = new Set( - ProductRepository.getAll().map(product => normalizeName(product.name)) + ProductRepository.getAll(householdId).map(product => normalizeName(product.name)) ); const seen = new Set(); const newProducts = []; @@ -104,12 +104,12 @@ class MenuService { addToShoppingList(householdId, name, menu) { - const products = ProductRepository.getAll(); + const products = ProductRepository.getAll(householdId); const productsByName = new Map( products.map(product => [normalizeName(product.name), product]) ); const insertProduct = db.prepare(` - INSERT INTO products (name, category_id, barcode) VALUES (?, NULL, NULL) + INSERT INTO products (name, category_id, barcode, household_id) VALUES (?, NULL, NULL, ?) `); const insertList = db.prepare(` INSERT INTO shopping_lists (household_id, name) VALUES (?, ?) @@ -132,7 +132,7 @@ class MenuService { let product = productsByName.get(key); if (!product) { - const result = insertProduct.run(ingredient.name); + const result = insertProduct.run(ingredient.name, householdId); product = { id: result.lastInsertRowid, name: ingredient.name }; productsByName.set(key, product); } diff --git a/services/ProductService.js b/services/ProductService.js index 29b53ed..51a4b59 100644 --- a/services/ProductService.js +++ b/services/ProductService.js @@ -5,9 +5,9 @@ require("../repositories/ProductRepository"); class ProductService { - getProducts() { + getProducts(householdId) { - return ProductRepository.getAll(); + return ProductRepository.getAll(householdId); } @@ -26,30 +26,30 @@ class ProductService { } - getProduct(id) { + getProduct(id, householdId) { - return ProductRepository.findById(id); + return ProductRepository.findById(id, householdId); } - updateProduct(id, data) { + updateProduct(id, householdId, data) { - return ProductRepository.update(id, data); + return ProductRepository.update(id, householdId, data); } - productHasListItems(id) { + productHasListItems(id, householdId) { - return ProductRepository.hasListItems(id); + return ProductRepository.hasListItems(id, householdId); } - deleteProduct(id) { + deleteProduct(id, householdId) { - return ProductRepository.delete(id); + return ProductRepository.delete(id, householdId); } diff --git a/sessions.sqlite b/sessions.sqlite index 9bd16f5..82b9c08 100644 Binary files a/sessions.sqlite and b/sessions.sqlite differ