Versie module 1 van 26-7
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# PantryHub
|
||||
|
||||
Een moderne boodschappenapp gebouwd met:
|
||||
|
||||
- Node.js
|
||||
- Express
|
||||
- SQLite
|
||||
- Socket.IO
|
||||
- Bootstrap (Tabler)
|
||||
- EJS
|
||||
- PWA
|
||||
|
||||
## Installatie
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open daarna:
|
||||
|
||||
http://localhost:3000
|
||||
|
||||
## Features
|
||||
|
||||
- Huishoudens
|
||||
- Gedeelde boodschappenlijsten
|
||||
- Slimme productdatabase
|
||||
- Realtime synchronisatie
|
||||
- Offline ondersteuning
|
||||
- Pushnotificaties
|
||||
- Barcode scanner
|
||||
@@ -0,0 +1,25 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const http = require("http");
|
||||
|
||||
const createApp = require("./config/express");
|
||||
|
||||
const app = createApp();
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
server.listen(PORT, () => {
|
||||
|
||||
console.log("");
|
||||
|
||||
console.log("===================================");
|
||||
|
||||
console.log("🚀 PantryHub gestart");
|
||||
|
||||
console.log(`🌍 http://localhost:${PORT}`);
|
||||
|
||||
console.log("===================================");
|
||||
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
|
||||
appName: "PantryHub",
|
||||
|
||||
port: process.env.PORT || 3000,
|
||||
|
||||
env: process.env.NODE_ENV,
|
||||
|
||||
database: process.env.DATABASE,
|
||||
|
||||
sessionSecret: process.env.SESSION_SECRET
|
||||
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
const Database = require("better-sqlite3");
|
||||
const path = require("path");
|
||||
|
||||
const databaseFile = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
process.env.DATABASE || "database.sqlite"
|
||||
);
|
||||
|
||||
const db = new Database(databaseFile);
|
||||
|
||||
db.pragma("journal_mode = WAL");
|
||||
db.pragma("foreign_keys = ON");
|
||||
|
||||
module.exports = db;
|
||||
@@ -0,0 +1,93 @@
|
||||
const express = require("express");
|
||||
const helmet = require("helmet");
|
||||
const compression = require("compression");
|
||||
const morgan = require("morgan");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const path = require("path");
|
||||
const expressLayouts = require("express-ejs-layouts");
|
||||
const flash = require("connect-flash");
|
||||
const userMiddleware = require("../middleware/user");
|
||||
|
||||
const session = require("./session");
|
||||
|
||||
|
||||
module.exports = () => {
|
||||
|
||||
const app = express();
|
||||
|
||||
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.set(
|
||||
"views",
|
||||
path.join(__dirname, "../views")
|
||||
);
|
||||
|
||||
|
||||
app.use(expressLayouts);
|
||||
|
||||
app.set(
|
||||
"layout",
|
||||
"layouts/main"
|
||||
);
|
||||
|
||||
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: false
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
app.use(compression());
|
||||
|
||||
app.use(morgan("dev"));
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
|
||||
app.use(
|
||||
express.urlencoded({
|
||||
extended: true
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.use(session);
|
||||
|
||||
app.use(userMiddleware);
|
||||
|
||||
app.use(flash());
|
||||
|
||||
|
||||
app.use(
|
||||
express.static(
|
||||
path.join(__dirname, "../public")
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
app.use(
|
||||
"/",
|
||||
require("../routes/auth")
|
||||
);
|
||||
|
||||
app.use(
|
||||
"/",
|
||||
require("../routes/web")
|
||||
);
|
||||
|
||||
|
||||
app.use((req, res) => {
|
||||
|
||||
res.status(404)
|
||||
.render("errors/404");
|
||||
|
||||
});
|
||||
|
||||
|
||||
return app;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
const session = require("express-session");
|
||||
const BetterSqlite3Store = require("better-sqlite3-session-store")(session);
|
||||
const Database = require("better-sqlite3");
|
||||
|
||||
const db = new Database("sessions.sqlite");
|
||||
|
||||
module.exports = session({
|
||||
|
||||
store: new BetterSqlite3Store({
|
||||
client: db,
|
||||
expired: {
|
||||
clear: true,
|
||||
intervalMs: 900000
|
||||
}
|
||||
}),
|
||||
|
||||
secret: process.env.SESSION_SECRET,
|
||||
|
||||
resave: false,
|
||||
|
||||
saveUninitialized: false,
|
||||
|
||||
cookie: {
|
||||
maxAge: 1000 * 60 * 60 * 24 * 30
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
const bcrypt = require("bcrypt");
|
||||
|
||||
const db = require("../config/database");
|
||||
const HouseholdService = require("../services/HouseholdService");
|
||||
|
||||
|
||||
|
||||
class AuthController {
|
||||
|
||||
|
||||
registerForm(req, res) {
|
||||
|
||||
res.render("auth/register", {
|
||||
title: "Registreren"
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
async register(req, res) {
|
||||
|
||||
const {
|
||||
name,
|
||||
email,
|
||||
password
|
||||
} = req.body;
|
||||
|
||||
|
||||
const existingUser = db.prepare(
|
||||
"SELECT id FROM users WHERE email = ?"
|
||||
).get(email);
|
||||
|
||||
|
||||
if (existingUser) {
|
||||
|
||||
req.flash(
|
||||
"error",
|
||||
"E-mailadres bestaat al"
|
||||
);
|
||||
|
||||
return res.redirect("/register");
|
||||
|
||||
}
|
||||
|
||||
|
||||
const passwordHash = await bcrypt.hash(
|
||||
password,
|
||||
12
|
||||
);
|
||||
|
||||
|
||||
const result = db.prepare(`
|
||||
|
||||
INSERT INTO users
|
||||
(
|
||||
name,
|
||||
email,
|
||||
password
|
||||
)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
|
||||
`).run(
|
||||
name,
|
||||
email,
|
||||
passwordHash
|
||||
);
|
||||
|
||||
|
||||
const userId = result.lastInsertRowid;
|
||||
|
||||
|
||||
HouseholdService.createForUser(
|
||||
userId,
|
||||
`${name} huishouden`
|
||||
);
|
||||
|
||||
|
||||
req.session.userId = userId;
|
||||
|
||||
|
||||
res.redirect("/");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
loginForm(req, res) {
|
||||
|
||||
res.render("auth/login", {
|
||||
title: "Inloggen"
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async login(req, res) {
|
||||
|
||||
const {
|
||||
email,
|
||||
password
|
||||
} = req.body;
|
||||
|
||||
|
||||
const user = db.prepare(
|
||||
"SELECT * FROM users WHERE email = ?"
|
||||
).get(email);
|
||||
|
||||
|
||||
|
||||
if (!user) {
|
||||
|
||||
req.flash(
|
||||
"error",
|
||||
"Ongeldige gegevens"
|
||||
);
|
||||
|
||||
return res.redirect("/login");
|
||||
|
||||
}
|
||||
|
||||
|
||||
const valid = await bcrypt.compare(
|
||||
password,
|
||||
user.password
|
||||
);
|
||||
|
||||
|
||||
if (!valid) {
|
||||
|
||||
req.flash(
|
||||
"error",
|
||||
"Ongeldige gegevens"
|
||||
);
|
||||
|
||||
return res.redirect("/login");
|
||||
|
||||
}
|
||||
|
||||
|
||||
req.session.userId = user.id;
|
||||
|
||||
|
||||
res.redirect("/");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
logout(req, res) {
|
||||
|
||||
req.session.destroy(() => {
|
||||
|
||||
res.redirect("/login");
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = new AuthController();
|
||||
@@ -0,0 +1,25 @@
|
||||
class DashboardController {
|
||||
|
||||
|
||||
index(req, res) {
|
||||
|
||||
|
||||
res.render(
|
||||
"dashboard/index",
|
||||
{
|
||||
|
||||
title: "Dashboard",
|
||||
|
||||
user: req.user
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = new DashboardController();
|
||||
@@ -0,0 +1,15 @@
|
||||
class HomeController {
|
||||
|
||||
index(req, res) {
|
||||
|
||||
res.render("home/index", {
|
||||
|
||||
title: "PantryHub"
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new HomeController();
|
||||
@@ -0,0 +1,21 @@
|
||||
class HouseholdController {
|
||||
|
||||
|
||||
index(req,res){
|
||||
|
||||
res.render(
|
||||
"household/index",
|
||||
{
|
||||
title:"Huishouden",
|
||||
user:req.user
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports =
|
||||
new HouseholdController();
|
||||
@@ -0,0 +1,35 @@
|
||||
const ProductService =
|
||||
require("../services/ProductService");
|
||||
|
||||
|
||||
class ProductController {
|
||||
|
||||
|
||||
index(req,res) {
|
||||
|
||||
|
||||
const products =
|
||||
ProductService.getProducts();
|
||||
|
||||
|
||||
res.render(
|
||||
"products/index",
|
||||
{
|
||||
|
||||
title:"Producten",
|
||||
|
||||
products
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports =
|
||||
new ProductController();
|
||||
Binary file not shown.
@@ -0,0 +1,214 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const db = require("../config/database");
|
||||
|
||||
|
||||
console.log("Database migratie gestart...");
|
||||
|
||||
|
||||
db.exec(`
|
||||
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
|
||||
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
|
||||
password TEXT NOT NULL,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS households (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS household_members (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
household_id INTEGER NOT NULL,
|
||||
|
||||
user_id INTEGER NOT NULL,
|
||||
|
||||
role TEXT NOT NULL DEFAULT 'member',
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
FOREIGN KEY (household_id)
|
||||
REFERENCES households(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES users(id)
|
||||
ON DELETE CASCADE
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
|
||||
icon TEXT,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
|
||||
category_id INTEGER,
|
||||
|
||||
barcode TEXT,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
FOREIGN KEY(category_id)
|
||||
REFERENCES categories(id)
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shopping_lists (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
household_id INTEGER NOT NULL,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
|
||||
archived INTEGER DEFAULT 0,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
FOREIGN KEY(household_id)
|
||||
REFERENCES households(id)
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shopping_list_items (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
list_id INTEGER NOT NULL,
|
||||
|
||||
product_id INTEGER NOT NULL,
|
||||
|
||||
amount REAL DEFAULT 1,
|
||||
|
||||
checked INTEGER DEFAULT 0,
|
||||
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
FOREIGN KEY(list_id)
|
||||
REFERENCES shopping_lists(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
|
||||
FOREIGN KEY(product_id)
|
||||
REFERENCES products(id)
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS stores (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_favorites (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
user_id INTEGER NOT NULL,
|
||||
|
||||
product_id INTEGER NOT NULL,
|
||||
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
UNIQUE(user_id, product_id),
|
||||
|
||||
|
||||
FOREIGN KEY(user_id)
|
||||
REFERENCES users(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
|
||||
FOREIGN KEY(product_id)
|
||||
REFERENCES products(id)
|
||||
ON DELETE CASCADE
|
||||
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_stores (
|
||||
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
product_id INTEGER NOT NULL,
|
||||
|
||||
store_id INTEGER NOT NULL,
|
||||
|
||||
|
||||
aisle TEXT,
|
||||
|
||||
|
||||
FOREIGN KEY(product_id)
|
||||
REFERENCES products(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
|
||||
FOREIGN KEY(store_id)
|
||||
REFERENCES stores(id)
|
||||
ON DELETE CASCADE
|
||||
|
||||
);
|
||||
|
||||
`);
|
||||
|
||||
|
||||
console.log("Database migratie voltooid.");
|
||||
@@ -0,0 +1,107 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const db = require("../config/database");
|
||||
|
||||
|
||||
const categories = [
|
||||
|
||||
["Groente", "🥬"],
|
||||
|
||||
["Fruit", "🍎"],
|
||||
|
||||
["Zuivel", "🥛"],
|
||||
|
||||
["Brood", "🍞"],
|
||||
|
||||
["Dranken", "🥤"],
|
||||
|
||||
["Huishouden", "🧻"]
|
||||
|
||||
];
|
||||
|
||||
const products = [
|
||||
|
||||
[
|
||||
"Melk",
|
||||
3
|
||||
],
|
||||
|
||||
[
|
||||
"Bananen",
|
||||
2
|
||||
],
|
||||
|
||||
[
|
||||
"Brood",
|
||||
4
|
||||
],
|
||||
|
||||
[
|
||||
"Koffie",
|
||||
5
|
||||
],
|
||||
|
||||
[
|
||||
"WC papier",
|
||||
6
|
||||
]
|
||||
|
||||
];
|
||||
|
||||
|
||||
const insertProduct = db.prepare(`
|
||||
|
||||
INSERT OR IGNORE INTO products
|
||||
|
||||
(
|
||||
name,
|
||||
category_id
|
||||
)
|
||||
|
||||
VALUES (?,?)
|
||||
|
||||
`);
|
||||
|
||||
|
||||
|
||||
|
||||
const seedProducts = db.transaction(() => {
|
||||
|
||||
|
||||
for(const product of products){
|
||||
|
||||
insertProduct.run(product);
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
seedProducts();
|
||||
|
||||
const insert = db.prepare(`
|
||||
|
||||
INSERT OR IGNORE INTO categories
|
||||
(name, icon)
|
||||
|
||||
VALUES (?,?)
|
||||
|
||||
`);
|
||||
|
||||
|
||||
const seed = db.transaction(() => {
|
||||
|
||||
for (const category of categories) {
|
||||
|
||||
insert.run(category);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
seed();
|
||||
|
||||
|
||||
console.log("Seed voltooid.");
|
||||
@@ -0,0 +1,13 @@
|
||||
module.exports = function(req, res, next) {
|
||||
|
||||
|
||||
if (!req.session.userId) {
|
||||
|
||||
return res.redirect("/login");
|
||||
|
||||
}
|
||||
|
||||
|
||||
next();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
const UserRepository =
|
||||
require("../repositories/UserRepository");
|
||||
|
||||
|
||||
module.exports = function userMiddleware(req, res, next) {
|
||||
|
||||
|
||||
if (req.session.userId) {
|
||||
|
||||
req.user =
|
||||
UserRepository.findById(
|
||||
req.session.userId
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
next();
|
||||
|
||||
};
|
||||
Generated
+2012
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "pantryhub",
|
||||
"version": "1.0.0",
|
||||
"description": "PantryHub - Realtime boodschappenapp",
|
||||
"private": true,
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js",
|
||||
"db:migrate": "node database/migrate.js",
|
||||
"db:seed": "node database/seed.js"
|
||||
},
|
||||
"keywords": [
|
||||
"shopping",
|
||||
"groceries",
|
||||
"pwa",
|
||||
"sqlite",
|
||||
"express",
|
||||
"socketio"
|
||||
],
|
||||
"author": "Jurgen",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-sqlite3": "^13.0.1",
|
||||
"better-sqlite3-session-store": "^0.1.0",
|
||||
"compression": "^1.8.1",
|
||||
"connect-flash": "^0.1.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"dotenv": "^17.4.2",
|
||||
"ejs": "^6.0.1",
|
||||
"express": "^5.2.1",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"express-session": "^1.19.0",
|
||||
"express-validator": "^7.3.2",
|
||||
"helmet": "^8.3.0",
|
||||
"morgan": "^1.11.0",
|
||||
"multer": "^2.2.0",
|
||||
"socket.io": "^4.8.3",
|
||||
"uuid": "^14.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.14"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
body {
|
||||
|
||||
background-color: #f6f8fb;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.navbar-brand {
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
const db = require("../config/database");
|
||||
|
||||
|
||||
class ProductRepository {
|
||||
|
||||
|
||||
getAll() {
|
||||
|
||||
return db.prepare(`
|
||||
|
||||
SELECT
|
||||
|
||||
products.*,
|
||||
|
||||
categories.name AS category_name,
|
||||
|
||||
categories.icon AS category_icon
|
||||
|
||||
|
||||
FROM products
|
||||
|
||||
|
||||
LEFT JOIN categories
|
||||
|
||||
ON categories.id = products.category_id
|
||||
|
||||
|
||||
ORDER BY products.name
|
||||
|
||||
`).all();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
findById(id) {
|
||||
|
||||
return db.prepare(`
|
||||
|
||||
SELECT *
|
||||
|
||||
FROM products
|
||||
|
||||
WHERE id = ?
|
||||
|
||||
`).get(id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
create(data) {
|
||||
|
||||
|
||||
return db.prepare(`
|
||||
|
||||
INSERT INTO products
|
||||
|
||||
(
|
||||
name,
|
||||
category_id,
|
||||
barcode
|
||||
)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
|
||||
`).run(
|
||||
|
||||
data.name,
|
||||
|
||||
data.category_id || null,
|
||||
|
||||
data.barcode || null
|
||||
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = new ProductRepository();
|
||||
@@ -0,0 +1,35 @@
|
||||
const db = require("../config/database");
|
||||
|
||||
|
||||
class UserRepository {
|
||||
|
||||
|
||||
findById(id) {
|
||||
|
||||
return db.prepare(`
|
||||
|
||||
SELECT
|
||||
users.*,
|
||||
household_members.household_id,
|
||||
household_members.role,
|
||||
households.name AS household_name
|
||||
|
||||
FROM users
|
||||
|
||||
LEFT JOIN household_members
|
||||
ON users.id = household_members.user_id
|
||||
|
||||
LEFT JOIN households
|
||||
ON households.id = household_members.household_id
|
||||
|
||||
WHERE users.id = ?
|
||||
|
||||
`).get(id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = new UserRepository();
|
||||
@@ -0,0 +1,40 @@
|
||||
const router = require("express").Router();
|
||||
|
||||
|
||||
const AuthController =
|
||||
require("../controllers/AuthController");
|
||||
|
||||
|
||||
router.get(
|
||||
"/register",
|
||||
AuthController.registerForm
|
||||
);
|
||||
|
||||
|
||||
router.post(
|
||||
"/register",
|
||||
AuthController.register
|
||||
);
|
||||
|
||||
|
||||
|
||||
router.get(
|
||||
"/login",
|
||||
AuthController.loginForm
|
||||
);
|
||||
|
||||
|
||||
router.post(
|
||||
"/login",
|
||||
AuthController.login
|
||||
);
|
||||
|
||||
|
||||
|
||||
router.get(
|
||||
"/logout",
|
||||
AuthController.logout
|
||||
);
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,38 @@
|
||||
const router = require("express").Router();
|
||||
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const DashboardController =
|
||||
require("../controllers/DashboardController");
|
||||
|
||||
const HouseholdController =
|
||||
require("../controllers/HouseholdController");
|
||||
|
||||
const ProductController =
|
||||
require("../controllers/ProductController");
|
||||
|
||||
console.log("AUTH:", auth);
|
||||
console.log("DASHBOARD INDEX:", DashboardController.index);
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
auth,
|
||||
DashboardController.index
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/household",
|
||||
auth,
|
||||
HouseholdController.index.bind(HouseholdController)
|
||||
);
|
||||
|
||||
router.get(
|
||||
|
||||
"/products",
|
||||
|
||||
auth,
|
||||
|
||||
ProductController.index.bind(ProductController)
|
||||
|
||||
);
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,59 @@
|
||||
const db = require("../config/database");
|
||||
|
||||
|
||||
class HouseholdService {
|
||||
|
||||
|
||||
createForUser(userId, name) {
|
||||
|
||||
|
||||
const create =
|
||||
db.transaction(() => {
|
||||
|
||||
|
||||
const household =
|
||||
db.prepare(`
|
||||
|
||||
INSERT INTO households
|
||||
(name)
|
||||
|
||||
VALUES (?)
|
||||
|
||||
`).run(name);
|
||||
|
||||
|
||||
|
||||
db.prepare(`
|
||||
|
||||
INSERT INTO household_members
|
||||
|
||||
(
|
||||
household_id,
|
||||
user_id,
|
||||
role
|
||||
)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
|
||||
`).run(
|
||||
household.lastInsertRowid,
|
||||
userId,
|
||||
"OWNER"
|
||||
);
|
||||
|
||||
|
||||
return household.lastInsertRowid;
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
return create();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = new HouseholdService();
|
||||
@@ -0,0 +1,26 @@
|
||||
const ProductRepository =
|
||||
require("../repositories/ProductRepository");
|
||||
|
||||
|
||||
class ProductService {
|
||||
|
||||
|
||||
getProducts() {
|
||||
|
||||
return ProductRepository.getAll();
|
||||
|
||||
}
|
||||
|
||||
|
||||
createProduct(data) {
|
||||
|
||||
return ProductRepository.create(data);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports =
|
||||
new ProductService();
|
||||
Binary file not shown.
@@ -0,0 +1,58 @@
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
|
||||
<h2>
|
||||
Inloggen
|
||||
</h2>
|
||||
|
||||
|
||||
<form method="post">
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<label class="form-label">
|
||||
Email
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="form-control"
|
||||
type="email"
|
||||
name="email"
|
||||
required>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<label class="form-label">
|
||||
Wachtwoord
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="form-control"
|
||||
type="password"
|
||||
name="password"
|
||||
required>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button class="btn btn-primary">
|
||||
|
||||
Inloggen
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,73 @@
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
|
||||
<h2>
|
||||
Account maken
|
||||
</h2>
|
||||
|
||||
|
||||
<form method="post">
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<label class="form-label">
|
||||
Naam
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="form-control"
|
||||
name="name"
|
||||
required>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<label class="form-label">
|
||||
Email
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="form-control"
|
||||
type="email"
|
||||
name="email"
|
||||
required>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<label class="form-label">
|
||||
Wachtwoord
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="form-control"
|
||||
type="password"
|
||||
name="password"
|
||||
required>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button class="btn btn-primary">
|
||||
|
||||
Registreren
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="mb-4">
|
||||
|
||||
<h1>
|
||||
Welkom <%= user.name %> 👋
|
||||
</h1>
|
||||
|
||||
|
||||
<p class="text-secondary">
|
||||
|
||||
Huishouden:
|
||||
<strong>
|
||||
<%= user.household_name || "Geen huishouden" %>
|
||||
</strong>
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<title>404</title>
|
||||
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css"
|
||||
rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container py-5">
|
||||
|
||||
<h1>404</h1>
|
||||
|
||||
<p>Pagina niet gevonden.</p>
|
||||
|
||||
<a
|
||||
href="/"
|
||||
class="btn btn-primary">
|
||||
|
||||
Terug
|
||||
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,69 @@
|
||||
<!doctype html>
|
||||
|
||||
<html lang="nl">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<title><%= title %></title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css"
|
||||
rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<div class="container-xl py-5">
|
||||
|
||||
<div class="text-center">
|
||||
|
||||
<h1 class="display-3">
|
||||
|
||||
🛒 PantryHub
|
||||
|
||||
</h1>
|
||||
|
||||
<p class="text-secondary">
|
||||
|
||||
De slimme boodschappenapp
|
||||
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<a
|
||||
href="/login"
|
||||
class="btn btn-primary">
|
||||
|
||||
Inloggen
|
||||
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/register"
|
||||
class="btn btn-success">
|
||||
|
||||
Registreren
|
||||
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
|
||||
<h2>
|
||||
<%= user.household_name %>
|
||||
</h2>
|
||||
|
||||
|
||||
<p>
|
||||
|
||||
Rol:
|
||||
|
||||
<strong>
|
||||
<%= user.role %>
|
||||
</strong>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<button class="btn btn-primary">
|
||||
|
||||
Lid uitnodigen
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<html lang="nl">
|
||||
|
||||
<head>
|
||||
|
||||
<%- include("../partials/head") %>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div class="page">
|
||||
|
||||
<%- include("../partials/navbar") %>
|
||||
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
|
||||
<div class="container-xl">
|
||||
|
||||
<%- body %>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<%- include("../partials/footer") %>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<footer class="footer footer-transparent">
|
||||
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="text-center">
|
||||
|
||||
PantryHub © <%= new Date().getFullYear() %>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
@@ -0,0 +1,18 @@
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
|
||||
<title>
|
||||
<%= title || "PantryHub" %>
|
||||
</title>
|
||||
|
||||
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css"
|
||||
rel="stylesheet">
|
||||
|
||||
|
||||
<link
|
||||
href="/css/style.css"
|
||||
rel="stylesheet">
|
||||
@@ -0,0 +1,14 @@
|
||||
<header class="navbar navbar-expand-md navbar-light d-print-none">
|
||||
|
||||
<div class="container-xl">
|
||||
|
||||
<a class="navbar-brand" href="/">
|
||||
|
||||
🛒 PantryHub
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</header>
|
||||
@@ -0,0 +1,49 @@
|
||||
<aside class="navbar navbar-vertical navbar-expand-lg">
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
|
||||
<div class="navbar-nav">
|
||||
|
||||
|
||||
<a class="nav-link" href="/">
|
||||
|
||||
🏠 Dashboard
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
<a class="nav-link" href="/lists">
|
||||
|
||||
🛒 Lijsten
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
<a class="nav-link" href="/products">
|
||||
|
||||
📦 Producten
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
<a class="nav-link" href="/household">
|
||||
|
||||
👨👩👧 Huishouden
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
<a class="nav-link" href="/settings">
|
||||
|
||||
⚙ Instellingen
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
@@ -0,0 +1,71 @@
|
||||
<div class="page-header mb-4">
|
||||
|
||||
<h1>
|
||||
|
||||
📦 Producten
|
||||
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
|
||||
<% if(products.length === 0) { %>
|
||||
|
||||
|
||||
<p>
|
||||
|
||||
Nog geen producten.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<% } %>
|
||||
|
||||
|
||||
|
||||
<div class="list-group">
|
||||
|
||||
|
||||
<% products.forEach(product => { %>
|
||||
|
||||
|
||||
<div class="list-group-item">
|
||||
|
||||
|
||||
<strong>
|
||||
|
||||
<%= product.name %>
|
||||
|
||||
</strong>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<span class="text-secondary">
|
||||
|
||||
<%= product.category_icon || "" %>
|
||||
|
||||
<%= product.category_name || "Geen categorie" %>
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<% }) %>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user