Compare commits
No commits in common. "03ea99d24d04e132324485eef5ce92f7b2847bbb" and "4df6629bad8b1b8541724334495f6fa9b11e1e91" have entirely different histories.
03ea99d24d
...
4df6629bad
1
z2/shop/.gitignore
vendored
1
z2/shop/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
node_modules
|
|
@ -1,13 +0,0 @@
|
|||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# install dependencies
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install --production
|
|
||||||
|
|
||||||
# copy app sources
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["npm", "run", "start-server"]
|
|
@ -1,45 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
|
|
||||||
const express = require("express");
|
|
||||||
const bodyParser = require("body-parser");
|
|
||||||
const mongoose = require("mongoose");
|
|
||||||
|
|
||||||
const errorController = require("./controllers/error");
|
|
||||||
const User = require("./models/user");
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.set("view engine", "ejs");
|
|
||||||
app.set("views", "views");
|
|
||||||
|
|
||||||
const adminRoutes = require("./routes/admin");
|
|
||||||
const shopRoutes = require("./routes/shop");
|
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
|
||||||
app.use(express.static(path.join(__dirname, "public")));
|
|
||||||
|
|
||||||
mongoose
|
|
||||||
.connect(process.env.MONGO_URL || "mongodb://mongo:27017/shop")
|
|
||||||
.then(async () => {
|
|
||||||
let user = await User.findOne();
|
|
||||||
if (!user) {
|
|
||||||
user = new User({
|
|
||||||
name: "Jawad",
|
|
||||||
email: "jawad@test.com",
|
|
||||||
cart: { items: [] },
|
|
||||||
});
|
|
||||||
await user.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
req.user = user;
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use("/admin", adminRoutes);
|
|
||||||
app.use(shopRoutes);
|
|
||||||
app.use(errorController.get404);
|
|
||||||
|
|
||||||
app.listen(3000, "0.0.0.0");
|
|
||||||
})
|
|
||||||
.catch((err) => console.log(err));
|
|
@ -1,93 +0,0 @@
|
|||||||
const Product = require('../models/product');
|
|
||||||
|
|
||||||
exports.getAddProduct = (req, res, next) => {
|
|
||||||
res.render('admin/edit-product', {
|
|
||||||
pageTitle: 'Add Product',
|
|
||||||
path: '/admin/add-product',
|
|
||||||
editing: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.postAddProduct = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
console.log('§ postAddProduct ➡️', req.body);
|
|
||||||
const { title, imageUrl, price, description } = req.body;
|
|
||||||
const product = new Product({ title, price, description, imageUrl });
|
|
||||||
await product.save();
|
|
||||||
console.log('✓ Product saved:', product._id);
|
|
||||||
return res.redirect('/admin/products');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('✗ Error in postAddProduct:', err);
|
|
||||||
next(err); // this will trigger your error‐handler (or crash if none)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getEditProduct = (req, res, next) => {
|
|
||||||
const editMode = req.query.edit;
|
|
||||||
if (!editMode) {
|
|
||||||
return res.redirect('/');
|
|
||||||
}
|
|
||||||
const prodId = req.params.productId;
|
|
||||||
Product.findById(prodId)
|
|
||||||
.then(product => {
|
|
||||||
if (!product) {
|
|
||||||
return res.redirect('/');
|
|
||||||
}
|
|
||||||
res.render('admin/edit-product', {
|
|
||||||
pageTitle: 'Edit Product',
|
|
||||||
path: '/admin/edit-product',
|
|
||||||
editing: editMode,
|
|
||||||
product: product
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.postEditProduct = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
console.log('§ postEditProduct ➡️', req.body);
|
|
||||||
const { productId, title, imageUrl, price, description } = req.body;
|
|
||||||
const product = await Product.findById(productId);
|
|
||||||
if (!product) {
|
|
||||||
console.warn('!! Tried to edit non‑existent product:', productId);
|
|
||||||
return res.redirect('/admin/products');
|
|
||||||
}
|
|
||||||
product.title = title;
|
|
||||||
product.price = price;
|
|
||||||
product.description = description;
|
|
||||||
product.imageUrl = imageUrl;
|
|
||||||
await product.save();
|
|
||||||
console.log('✓ Product updated:', product._id);
|
|
||||||
return res.redirect('/admin/products');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('✗ Error in postEditProduct:', err);
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getProducts = (req, res, next) => {
|
|
||||||
Product.find()
|
|
||||||
// .select('title price -_id')
|
|
||||||
// .populate('userId', 'name')
|
|
||||||
.then(products => {
|
|
||||||
console.log(products);
|
|
||||||
res.render('admin/products', {
|
|
||||||
prods: products,
|
|
||||||
pageTitle: 'Admin Products',
|
|
||||||
path: '/admin/products'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.postDeleteProduct = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const prodId = req.body.productId;
|
|
||||||
// Mongoose 6+: use findByIdAndDelete
|
|
||||||
await Product.findByIdAndDelete(prodId);
|
|
||||||
return res.redirect('/admin/products');
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,3 +0,0 @@
|
|||||||
exports.get404 = (req, res, next) => {
|
|
||||||
res.status(404).render('404', { pageTitle: 'Page Not Found', path: '/404' });
|
|
||||||
};
|
|
@ -1,127 +0,0 @@
|
|||||||
const Product = require('../models/product');
|
|
||||||
const Order = require('../models/order');
|
|
||||||
|
|
||||||
exports.getProducts = (req, res, next) => {
|
|
||||||
Product.find()
|
|
||||||
.then(products => {
|
|
||||||
console.log(products);
|
|
||||||
res.render('shop/product-list', {
|
|
||||||
prods: products,
|
|
||||||
pageTitle: 'All Products',
|
|
||||||
path: '/products'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getProduct = (req, res, next) => {
|
|
||||||
const prodId = req.params.productId;
|
|
||||||
Product.findById(prodId)
|
|
||||||
.then(product => {
|
|
||||||
res.render('shop/product-detail', {
|
|
||||||
product: product,
|
|
||||||
pageTitle: product.title,
|
|
||||||
path: '/products'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getIndex = (req, res, next) => {
|
|
||||||
Product.find()
|
|
||||||
.then(products => {
|
|
||||||
res.render('shop/index', {
|
|
||||||
prods: products,
|
|
||||||
pageTitle: 'Shop',
|
|
||||||
path: '/'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getCart = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
// populate() now returns a Promise in Mongoose 6+
|
|
||||||
const userPop = await req.user.populate('cart.items.productId');
|
|
||||||
// drop any cart‐items whose product was deleted
|
|
||||||
const cartItems = userPop.cart.items.filter(item => item.productId);
|
|
||||||
const products = cartItems.map(item => ({
|
|
||||||
productId: item.productId._id,
|
|
||||||
title: item.productId.title,
|
|
||||||
price: item.productId.price,
|
|
||||||
imageUrl: item.productId.imageUrl,
|
|
||||||
quantity: item.quantity
|
|
||||||
}));
|
|
||||||
res.render('shop/cart', {
|
|
||||||
pageTitle: 'Your Cart',
|
|
||||||
path: '/cart',
|
|
||||||
products
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.postCart = (req, res, next) => {
|
|
||||||
const prodId = req.body.productId;
|
|
||||||
Product.findById(prodId)
|
|
||||||
.then(product => {
|
|
||||||
return req.user.addToCart(product);
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
console.log(result);
|
|
||||||
res.redirect('/cart');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.postCartDeleteProduct = (req, res, next) => {
|
|
||||||
const prodId = req.body.productId;
|
|
||||||
req.user
|
|
||||||
.removeFromCart(prodId)
|
|
||||||
.then(result => {
|
|
||||||
res.redirect('/cart');
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.postOrder = (req, res, next) => {
|
|
||||||
req.user
|
|
||||||
.populate('cart.items.productId')
|
|
||||||
.execPopulate()
|
|
||||||
.then(user => {
|
|
||||||
const products = user.cart.items.map(i => {
|
|
||||||
return { quantity: i.quantity, product: { ...i.productId._doc } };
|
|
||||||
});
|
|
||||||
const order = new Order({
|
|
||||||
user: {
|
|
||||||
name: req.user.name,
|
|
||||||
userId: req.user
|
|
||||||
},
|
|
||||||
products: products
|
|
||||||
});
|
|
||||||
return order.save();
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
return req.user.clearCart();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
res.redirect('/orders');
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getOrders = (req, res, next) => {
|
|
||||||
Order.find({ 'user.userId': req.user._id })
|
|
||||||
.then(orders => {
|
|
||||||
res.render('shop/orders', {
|
|
||||||
path: '/orders',
|
|
||||||
pageTitle: 'Your Orders',
|
|
||||||
orders: orders
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
{"products":[{"id":"0.41607315815753076","qty":1}],"totalPrice":12}
|
|
@ -1 +0,0 @@
|
|||||||
[{"id":"123245","title":"A Book","imageUrl":"https://www.publicdomainpictures.net/pictures/10000/velka/1-1210009435EGmE.jpg","description":"This is an awesome book!","price":"19"},{"id":"0.41607315815753076","title":"fasfd","imageUrl":"fdasfs","description":"fadsfads","price":"12"}]
|
|
@ -1,25 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: shop-deployment
|
|
||||||
namespace: shop-ns
|
|
||||||
spec:
|
|
||||||
replicas: 2
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: shop-app
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: shop-app
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: shop-app
|
|
||||||
image: shop-app:latest
|
|
||||||
imagePullPolicy: IfNotPresent # ← add this
|
|
||||||
|
|
||||||
ports:
|
|
||||||
- containerPort: 3000
|
|
||||||
env:
|
|
||||||
- name: MONGO_URL
|
|
||||||
value: "mongodb://mongo:27017/shop"
|
|
@ -1,4 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: shop-ns
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
eval $(minikube docker-env)
|
|
||||||
# build your Node image in Minikube’s Docker daemon
|
|
||||||
cd ..
|
|
||||||
docker build -t shop-app:latest .
|
|
||||||
cd kubernetes
|
|
||||||
# create namespace & Mongo PV/PVC/StatefulSet
|
|
||||||
kubectl apply -f namespace.yaml
|
|
||||||
kubectl apply -n shop-ns -f statefulset.yaml
|
|
@ -1,13 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: shop-service
|
|
||||||
namespace: shop-ns
|
|
||||||
spec:
|
|
||||||
type: NodePort
|
|
||||||
selector:
|
|
||||||
app: shop-app
|
|
||||||
ports:
|
|
||||||
- port: 3000
|
|
||||||
targetPort: 3000
|
|
||||||
nodePort: 30000
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
kubectl apply -n shop-ns -f deployment.yaml
|
|
||||||
kubectl apply -n shop-ns -f service.yaml
|
|
@ -1,67 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolume
|
|
||||||
metadata:
|
|
||||||
name: mongo-pv
|
|
||||||
namespace: shop-ns
|
|
||||||
spec:
|
|
||||||
capacity:
|
|
||||||
storage: 1Gi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
hostPath:
|
|
||||||
path: /data/db
|
|
||||||
persistentVolumeReclaimPolicy: Retain
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: mongo-pvc
|
|
||||||
namespace: shop-ns
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 1Gi
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: mongo
|
|
||||||
namespace: shop-ns
|
|
||||||
spec:
|
|
||||||
clusterIP: None
|
|
||||||
selector:
|
|
||||||
app: mongo
|
|
||||||
ports:
|
|
||||||
- port: 27017
|
|
||||||
name: mongo
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: StatefulSet
|
|
||||||
metadata:
|
|
||||||
name: mongo
|
|
||||||
namespace: shop-ns
|
|
||||||
spec:
|
|
||||||
serviceName: "mongo"
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: mongo
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: mongo
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: mongo
|
|
||||||
image: mongo:5.0
|
|
||||||
ports:
|
|
||||||
- containerPort: 27017
|
|
||||||
volumeMounts:
|
|
||||||
- name: mongo-pvc
|
|
||||||
mountPath: /data/db
|
|
||||||
volumes:
|
|
||||||
- name: mongo-pvc
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: mongo-pvc
|
|
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
kubectl delete -n shop-ns -f service.yaml
|
|
||||||
kubectl delete -n shop-ns -f deployment.yaml
|
|
||||||
kubectl delete namespace shop-ns
|
|
@ -1,25 +0,0 @@
|
|||||||
const mongoose = require('mongoose');
|
|
||||||
|
|
||||||
const Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
const orderSchema = new Schema({
|
|
||||||
products: [
|
|
||||||
{
|
|
||||||
product: { type: Object, required: true },
|
|
||||||
quantity: { type: Number, required: true }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
user: {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
required: true,
|
|
||||||
ref: 'User'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = mongoose.model('Order', orderSchema);
|
|
@ -1,90 +0,0 @@
|
|||||||
const mongoose = require('mongoose');
|
|
||||||
const Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
const productSchema = new Schema({
|
|
||||||
title: { type: String, required: true },
|
|
||||||
price: { type: Number, required: true },
|
|
||||||
description:{ type: String, required: true },
|
|
||||||
imageUrl: { type: String, required: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = mongoose.model('Product', productSchema);
|
|
||||||
|
|
||||||
// const mongodb = require('mongodb');
|
|
||||||
// const getDb = require('../util/database').getDb;
|
|
||||||
|
|
||||||
// class Product {
|
|
||||||
// constructor(title, price, description, imageUrl, id, userId) {
|
|
||||||
// this.title = title;
|
|
||||||
// this.price = price;
|
|
||||||
// this.description = description;
|
|
||||||
// this.imageUrl = imageUrl;
|
|
||||||
// this._id = id ? new mongodb.ObjectId(id) : null;
|
|
||||||
// this.userId = userId;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// save() {
|
|
||||||
// const db = getDb();
|
|
||||||
// let dbOp;
|
|
||||||
// if (this._id) {
|
|
||||||
// // Update the product
|
|
||||||
// dbOp = db
|
|
||||||
// .collection('products')
|
|
||||||
// .updateOne({ _id: this._id }, { $set: this });
|
|
||||||
// } else {
|
|
||||||
// dbOp = db.collection('products').insertOne(this);
|
|
||||||
// }
|
|
||||||
// return dbOp
|
|
||||||
// .then(result => {
|
|
||||||
// console.log(result);
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static fetchAll() {
|
|
||||||
// const db = getDb();
|
|
||||||
// return db
|
|
||||||
// .collection('products')
|
|
||||||
// .find()
|
|
||||||
// .toArray()
|
|
||||||
// .then(products => {
|
|
||||||
// console.log(products);
|
|
||||||
// return products;
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static findById(prodId) {
|
|
||||||
// const db = getDb();
|
|
||||||
// return db
|
|
||||||
// .collection('products')
|
|
||||||
// .find({ _id: new mongodb.ObjectId(prodId) })
|
|
||||||
// .next()
|
|
||||||
// .then(product => {
|
|
||||||
// console.log(product);
|
|
||||||
// return product;
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static deleteById(prodId) {
|
|
||||||
// const db = getDb();
|
|
||||||
// return db
|
|
||||||
// .collection('products')
|
|
||||||
// .deleteOne({ _id: new mongodb.ObjectId(prodId) })
|
|
||||||
// .then(result => {
|
|
||||||
// console.log('Deleted');
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// module.exports = Product;
|
|
@ -1,193 +0,0 @@
|
|||||||
const mongoose = require('mongoose');
|
|
||||||
|
|
||||||
const Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
const userSchema = new Schema({
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
cart: {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
productId: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'Product',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
quantity: { type: Number, required: true }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
userSchema.methods.addToCart = function(product) {
|
|
||||||
const cartProductIndex = this.cart.items.findIndex(cp => {
|
|
||||||
return cp.productId.toString() === product._id.toString();
|
|
||||||
});
|
|
||||||
let newQuantity = 1;
|
|
||||||
const updatedCartItems = [...this.cart.items];
|
|
||||||
|
|
||||||
if (cartProductIndex >= 0) {
|
|
||||||
newQuantity = this.cart.items[cartProductIndex].quantity + 1;
|
|
||||||
updatedCartItems[cartProductIndex].quantity = newQuantity;
|
|
||||||
} else {
|
|
||||||
updatedCartItems.push({
|
|
||||||
productId: product._id,
|
|
||||||
quantity: newQuantity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const updatedCart = {
|
|
||||||
items: updatedCartItems
|
|
||||||
};
|
|
||||||
this.cart = updatedCart;
|
|
||||||
return this.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
userSchema.methods.removeFromCart = function(productId) {
|
|
||||||
const updatedCartItems = this.cart.items.filter(item => {
|
|
||||||
return item.productId.toString() !== productId.toString();
|
|
||||||
});
|
|
||||||
this.cart.items = updatedCartItems;
|
|
||||||
return this.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
userSchema.methods.clearCart = function() {
|
|
||||||
this.cart = { items: [] };
|
|
||||||
return this.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = mongoose.model('User', userSchema);
|
|
||||||
|
|
||||||
// const mongodb = require('mongodb');
|
|
||||||
// const getDb = require('../util/database').getDb;
|
|
||||||
|
|
||||||
// const ObjectId = mongodb.ObjectId;
|
|
||||||
|
|
||||||
// class User {
|
|
||||||
// constructor(username, email, cart, id) {
|
|
||||||
// this.name = username;
|
|
||||||
// this.email = email;
|
|
||||||
// this.cart = cart; // {items: []}
|
|
||||||
// this._id = id;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// save() {
|
|
||||||
// const db = getDb();
|
|
||||||
// return db.collection('users').insertOne(this);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// addToCart(product) {
|
|
||||||
// const cartProductIndex = this.cart.items.findIndex(cp => {
|
|
||||||
// return cp.productId.toString() === product._id.toString();
|
|
||||||
// });
|
|
||||||
// let newQuantity = 1;
|
|
||||||
// const updatedCartItems = [...this.cart.items];
|
|
||||||
|
|
||||||
// if (cartProductIndex >= 0) {
|
|
||||||
// newQuantity = this.cart.items[cartProductIndex].quantity + 1;
|
|
||||||
// updatedCartItems[cartProductIndex].quantity = newQuantity;
|
|
||||||
// } else {
|
|
||||||
// updatedCartItems.push({
|
|
||||||
// productId: new ObjectId(product._id),
|
|
||||||
// quantity: newQuantity
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// const updatedCart = {
|
|
||||||
// items: updatedCartItems
|
|
||||||
// };
|
|
||||||
// const db = getDb();
|
|
||||||
// return db
|
|
||||||
// .collection('users')
|
|
||||||
// .updateOne(
|
|
||||||
// { _id: new ObjectId(this._id) },
|
|
||||||
// { $set: { cart: updatedCart } }
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getCart() {
|
|
||||||
// const db = getDb();
|
|
||||||
// const productIds = this.cart.items.map(i => {
|
|
||||||
// return i.productId;
|
|
||||||
// });
|
|
||||||
// return db
|
|
||||||
// .collection('products')
|
|
||||||
// .find({ _id: { $in: productIds } })
|
|
||||||
// .toArray()
|
|
||||||
// .then(products => {
|
|
||||||
// return products.map(p => {
|
|
||||||
// return {
|
|
||||||
// ...p,
|
|
||||||
// quantity: this.cart.items.find(i => {
|
|
||||||
// return i.productId.toString() === p._id.toString();
|
|
||||||
// }).quantity
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// deleteItemFromCart(productId) {
|
|
||||||
// const updatedCartItems = this.cart.items.filter(item => {
|
|
||||||
// return item.productId.toString() !== productId.toString();
|
|
||||||
// });
|
|
||||||
// const db = getDb();
|
|
||||||
// return db
|
|
||||||
// .collection('users')
|
|
||||||
// .updateOne(
|
|
||||||
// { _id: new ObjectId(this._id) },
|
|
||||||
// { $set: { cart: { items: updatedCartItems } } }
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// addOrder() {
|
|
||||||
// const db = getDb();
|
|
||||||
// return this.getCart()
|
|
||||||
// .then(products => {
|
|
||||||
// const order = {
|
|
||||||
// items: products,
|
|
||||||
// user: {
|
|
||||||
// _id: new ObjectId(this._id),
|
|
||||||
// name: this.name
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// return db.collection('orders').insertOne(order);
|
|
||||||
// })
|
|
||||||
// .then(result => {
|
|
||||||
// this.cart = { items: [] };
|
|
||||||
// return db
|
|
||||||
// .collection('users')
|
|
||||||
// .updateOne(
|
|
||||||
// { _id: new ObjectId(this._id) },
|
|
||||||
// { $set: { cart: { items: [] } } }
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getOrders() {
|
|
||||||
// const db = getDb();
|
|
||||||
// return db
|
|
||||||
// .collection('orders')
|
|
||||||
// .find({ 'user._id': new ObjectId(this._id) })
|
|
||||||
// .toArray();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static findById(userId) {
|
|
||||||
// const db = getDb();
|
|
||||||
// return db
|
|
||||||
// .collection('users')
|
|
||||||
// .findOne({ _id: new ObjectId(userId) })
|
|
||||||
// .then(user => {
|
|
||||||
// console.log(user);
|
|
||||||
// return user;
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// module.exports = User;
|
|
2994
z2/shop/package-lock.json
generated
2994
z2/shop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nodejs-complete-guide",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Complete Node.js Guide",
|
|
||||||
"main": "app.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"start": "nodemon app.js",
|
|
||||||
"start-server": "node app.js"
|
|
||||||
},
|
|
||||||
"author": "Maximilian Schwarzmüller",
|
|
||||||
"license": "ISC",
|
|
||||||
"devDependencies": {
|
|
||||||
"nodemon": "^3.1.9"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"body-parser": "^1.18.3",
|
|
||||||
"ejs": "^3.1.10",
|
|
||||||
"express": "^4.16.3",
|
|
||||||
"express-handlebars": "^8.0.1",
|
|
||||||
"mongodb": "^3.1.6",
|
|
||||||
"mongoose": "^8.13.2",
|
|
||||||
"mysql2": "^3.14.0",
|
|
||||||
"pug": "^3.0.3",
|
|
||||||
"sequelize": "^6.37.7"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
.cart__item-list {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: auto;
|
|
||||||
width: 40rem;
|
|
||||||
max-width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart__item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 1rem;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart__item h1,
|
|
||||||
.cart__item h2 {
|
|
||||||
margin-right: 1rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
.form-control {
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control label,
|
|
||||||
.form-control input,
|
|
||||||
.form-control textarea {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control input,
|
|
||||||
.form-control textarea {
|
|
||||||
border: 1px solid #a1a1a1;
|
|
||||||
font: inherit;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control input:focus,
|
|
||||||
.form-control textarea:focus {
|
|
||||||
outline-color: #00695c;
|
|
||||||
}
|
|
@ -1,227 +0,0 @@
|
|||||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 1rem;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centered {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
height: 20rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image img {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-header {
|
|
||||||
width: 100%;
|
|
||||||
height: 3.5rem;
|
|
||||||
background-color: #00695c;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-header__nav {
|
|
||||||
height: 100%;
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-header__item-list {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-header__item {
|
|
||||||
margin: 0 1rem;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-header__item a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-header__item a:hover,
|
|
||||||
.main-header__item a:active,
|
|
||||||
.main-header__item a.active {
|
|
||||||
color: #ffeb3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav {
|
|
||||||
width: 30rem;
|
|
||||||
height: 100vh;
|
|
||||||
max-width: 90%;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
background: white;
|
|
||||||
z-index: 10;
|
|
||||||
padding: 2rem 1rem 1rem 2rem;
|
|
||||||
transform: translateX(-100%);
|
|
||||||
transition: transform 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav.open {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav__item-list {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav__item {
|
|
||||||
margin: 1rem;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav__item a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: black;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
padding: 0.5rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav__item a:active,
|
|
||||||
.mobile-nav__item a:hover,
|
|
||||||
.mobile-nav__item a.active {
|
|
||||||
background: #00695c;
|
|
||||||
color: white;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu-toggle {
|
|
||||||
border: 1px solid white;
|
|
||||||
font: inherit;
|
|
||||||
padding: 0.5rem;
|
|
||||||
display: block;
|
|
||||||
background: transparent;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu-toggle:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu-toggle:active,
|
|
||||||
#side-menu-toggle:hover {
|
|
||||||
color: #ffeb3b;
|
|
||||||
border-color: #ffeb3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.backdrop {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: 5;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__header,
|
|
||||||
.card__content {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__header h1,
|
|
||||||
.card__content h1,
|
|
||||||
.card__content h2,
|
|
||||||
.card__content p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__image img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__actions {
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__actions button,
|
|
||||||
.card__actions a {
|
|
||||||
margin: 0 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25rem 1rem;
|
|
||||||
text-decoration: none;
|
|
||||||
font: inherit;
|
|
||||||
border: 1px solid #00695c;
|
|
||||||
color: #00695c;
|
|
||||||
background: white;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover,
|
|
||||||
.btn:active {
|
|
||||||
background-color: #00695c;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.danger {
|
|
||||||
color: red;
|
|
||||||
border-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.danger:hover,
|
|
||||||
.btn.danger:active {
|
|
||||||
background: red;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.main-header__nav {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu-toggle {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
.orders {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orders__item h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orders__item {
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orders__products {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orders__products-item {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid #00695c;
|
|
||||||
color: #00695c;
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
.product-form {
|
|
||||||
width: 20rem;
|
|
||||||
max-width: 90%;
|
|
||||||
margin: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-item {
|
|
||||||
width: 20rem;
|
|
||||||
max-width: 95%;
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product__title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product__price {
|
|
||||||
text-align: center;
|
|
||||||
color: #4d4d4d;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product__description {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
const backdrop = document.querySelector('.backdrop');
|
|
||||||
const sideDrawer = document.querySelector('.mobile-nav');
|
|
||||||
const menuToggle = document.querySelector('#side-menu-toggle');
|
|
||||||
|
|
||||||
function backdropClickHandler() {
|
|
||||||
backdrop.style.display = 'none';
|
|
||||||
sideDrawer.classList.remove('open');
|
|
||||||
}
|
|
||||||
|
|
||||||
function menuToggleClickHandler() {
|
|
||||||
backdrop.style.display = 'block';
|
|
||||||
sideDrawer.classList.add('open');
|
|
||||||
}
|
|
||||||
|
|
||||||
backdrop.addEventListener('click', backdropClickHandler);
|
|
||||||
menuToggle.addEventListener('click', menuToggleClickHandler);
|
|
@ -1,24 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
const express = require('express');
|
|
||||||
|
|
||||||
const adminController = require('../controllers/admin');
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
// /admin/add-product => GET
|
|
||||||
router.get('/add-product', adminController.getAddProduct);
|
|
||||||
|
|
||||||
// /admin/products => GET
|
|
||||||
router.get('/products', adminController.getProducts);
|
|
||||||
|
|
||||||
// /admin/add-product => POST
|
|
||||||
router.post('/add-product', adminController.postAddProduct);
|
|
||||||
|
|
||||||
router.get('/edit-product/:productId', adminController.getEditProduct);
|
|
||||||
|
|
||||||
router.post('/edit-product', adminController.postEditProduct);
|
|
||||||
|
|
||||||
router.post('/delete-product', adminController.postDeleteProduct);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
@ -1,25 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
const express = require('express');
|
|
||||||
|
|
||||||
const shopController = require('../controllers/shop');
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.get('/', shopController.getIndex);
|
|
||||||
|
|
||||||
router.get('/products', shopController.getProducts);
|
|
||||||
|
|
||||||
router.get('/products/:productId', shopController.getProduct);
|
|
||||||
|
|
||||||
router.get('/cart', shopController.getCart);
|
|
||||||
|
|
||||||
router.post('/cart', shopController.postCart);
|
|
||||||
|
|
||||||
router.post('/cart-delete-item', shopController.postCartDeleteProduct);
|
|
||||||
|
|
||||||
router.post('/create-order', shopController.postOrder);
|
|
||||||
|
|
||||||
router.get('/orders', shopController.getOrders);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
@ -1,3 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = path.dirname(process.mainModule.filename);
|
|
@ -1,8 +0,0 @@
|
|||||||
<%- include('includes/head.ejs') %>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('includes/navigation.ejs') %>
|
|
||||||
<h1>Page Not Found!</h1>
|
|
||||||
|
|
||||||
<%- include('includes/end.ejs') %>
|
|
@ -1,34 +0,0 @@
|
|||||||
<%- include('../includes/head.ejs') %>
|
|
||||||
<link rel="stylesheet" href="/css/forms.css">
|
|
||||||
<link rel="stylesheet" href="/css/product.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('../includes/navigation.ejs') %>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<form class="product-form" action="/admin/<% if (editing) { %>edit-product<% } else { %>add-product<% } %>" method="POST">
|
|
||||||
<div class="form-control">
|
|
||||||
<label for="title">Title</label>
|
|
||||||
<input type="text" name="title" id="title" value="<% if (editing) { %><%= product.title %><% } %>">
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<label for="imageUrl">Image URL</label>
|
|
||||||
<input type="text" name="imageUrl" id="imageUrl" value="<% if (editing) { %><%= product.imageUrl %><% } %>">
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<label for="price">Price</label>
|
|
||||||
<input type="number" name="price" id="price" step="0.01" value="<% if (editing) { %><%= product.price %><% } %>">
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<label for="description">Description</label>
|
|
||||||
<textarea name="description" id="description" rows="5"><% if (editing) { %><%= product.description %><% } %></textarea>
|
|
||||||
</div>
|
|
||||||
<% if (editing) { %>
|
|
||||||
<input type="hidden" value="<%= product._id %>" name="productId">
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<button class="btn" type="submit"><% if (editing) { %>Update Product<% } else { %>Add Product<% } %></button>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
<%- include('../includes/end.ejs') %>
|
|
@ -1,44 +0,0 @@
|
|||||||
<%- include('../includes/head.ejs') %>
|
|
||||||
<link rel="stylesheet" href="/css/product.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('../includes/navigation.ejs') %>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<% if (prods.length > 0) { %>
|
|
||||||
<div class="grid">
|
|
||||||
<% for (let product of prods) { %>
|
|
||||||
<article class="card product-item">
|
|
||||||
<header class="card__header">
|
|
||||||
<h1 class="product__title">
|
|
||||||
<%= product.title %>
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
<div class="card__image">
|
|
||||||
<img src="<%= product.imageUrl %>" alt="<%= product.title %>">
|
|
||||||
</div>
|
|
||||||
<div class="card__content">
|
|
||||||
<h2 class="product__price">$
|
|
||||||
<%= product.price %>
|
|
||||||
</h2>
|
|
||||||
<p class="product__description">
|
|
||||||
<%= product.description %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="card__actions">
|
|
||||||
<a href="/admin/edit-product/<%= product._id %>?edit=true" class="btn">Edit</a>
|
|
||||||
<form action="/admin/delete-product" method="POST">
|
|
||||||
<input type="hidden" value="<%= product._id %>" name="productId">
|
|
||||||
<button class="btn" type="submit">Delete</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
<% } else { %>
|
|
||||||
<h1>No Products Found!</h1>
|
|
||||||
<% } %>
|
|
||||||
</main>
|
|
||||||
<%- include('../includes/end.ejs') %>
|
|
@ -1,4 +0,0 @@
|
|||||||
<form action="/cart" method="post">
|
|
||||||
<button class="btn" type="submit">Add to Cart</button>
|
|
||||||
<input type="hidden" name="productId" value="<%= product._id %>">
|
|
||||||
</form>
|
|
@ -1,4 +0,0 @@
|
|||||||
<script src="/js/main.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,9 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title><%= pageTitle %></title>
|
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
|
@ -1,53 +0,0 @@
|
|||||||
<div class="backdrop"></div>
|
|
||||||
<header class="main-header">
|
|
||||||
<button id="side-menu-toggle">Menu</button>
|
|
||||||
<nav class="main-header__nav">
|
|
||||||
<ul class="main-header__item-list">
|
|
||||||
<li class="main-header__item">
|
|
||||||
<a class="<%= path === '/' ? 'active' : '' %>" href="/">Shop</a>
|
|
||||||
</li>
|
|
||||||
<li class="main-header__item">
|
|
||||||
<a class="<%= path === '/products' ? 'active' : '' %>" href="/products">Products</a>
|
|
||||||
</li>
|
|
||||||
<li class="main-header__item">
|
|
||||||
<a class="<%= path === '/cart' ? 'active' : '' %>" href="/cart">Cart</a>
|
|
||||||
</li>
|
|
||||||
<li class="main-header__item">
|
|
||||||
<a class="<%= path === '/orders' ? 'active' : '' %>" href="/orders">Orders</a>
|
|
||||||
</li>
|
|
||||||
<li class="main-header__item">
|
|
||||||
<a class="<%= path === '/admin/add-product' ? 'active' : '' %>" href="/admin/add-product">Add Product
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="main-header__item">
|
|
||||||
<a class="<%= path === '/admin/products' ? 'active' : '' %>" href="/admin/products">Admin Products
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<nav class="mobile-nav">
|
|
||||||
<ul class="mobile-nav__item-list">
|
|
||||||
<li class="mobile-nav__item">
|
|
||||||
<a class="<%= path === '/' ? 'active' : '' %>" href="/">Shop</a>
|
|
||||||
</li>
|
|
||||||
<li class="mobile-nav__item">
|
|
||||||
<a class="<%= path === '/products' ? 'active' : '' %>" href="/products">Products</a>
|
|
||||||
</li>
|
|
||||||
<li class="mobile-nav__item">
|
|
||||||
<a class="<%= path === '/cart' ? 'active' : '' %>" href="/cart">Cart</a>
|
|
||||||
</li>
|
|
||||||
<li class="mobile-nav__item">
|
|
||||||
<a class="<%= path === '/orders' ? 'active' : '' %>" href="/orders">Orders</a>
|
|
||||||
</li>
|
|
||||||
<li class="mobile-nav__item">
|
|
||||||
<a class="<%= path === '/admin/add-product' ? 'active' : '' %>" href="/admin/add-product">Add Product
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="mobile-nav__item">
|
|
||||||
<a class="<%= path === '/admin/products' ? 'active' : '' %>" href="/admin/products">Admin Products
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
@ -1,32 +0,0 @@
|
|||||||
<%- include('../includes/head.ejs') %>
|
|
||||||
<link rel="stylesheet" href="/css/cart.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('../includes/navigation.ejs') %>
|
|
||||||
<main>
|
|
||||||
<% if (products.length > 0) { %>
|
|
||||||
<ul class="cart__list">
|
|
||||||
<% products.forEach(p => { %>
|
|
||||||
<li class="cart__item">
|
|
||||||
<h1><%= p.title %></h1>
|
|
||||||
<h2>Quantity: <%= p.quantity %></h2>
|
|
||||||
<form action="/cart-delete-item" method="POST">
|
|
||||||
<input type="hidden" name="productId" value="<%= p.productId %>">
|
|
||||||
<button type="submit">Remove</button>
|
|
||||||
</form>
|
|
||||||
</li>
|
|
||||||
<% }) %>
|
|
||||||
</ul>
|
|
||||||
<hr>
|
|
||||||
<div class="centered">
|
|
||||||
<form action="/create-order" method="POST">
|
|
||||||
<button type="submit" class="btn">Order Now!</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% } else { %>
|
|
||||||
<h1>No Products in Cart!</h1>
|
|
||||||
<% } %>
|
|
||||||
</main>
|
|
||||||
<%- include('../includes/end.ejs') %>
|
|
@ -1,35 +0,0 @@
|
|||||||
<%- include('../includes/head.ejs') %>
|
|
||||||
<link rel="stylesheet" href="/css/product.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('../includes/navigation.ejs') %>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<% if (prods.length > 0) { %>
|
|
||||||
<div class="grid">
|
|
||||||
<% for (let product of prods) { %>
|
|
||||||
<article class="card product-item">
|
|
||||||
<header class="card__header">
|
|
||||||
<h1 class="product__title"><%= product.title %></h1>
|
|
||||||
</header>
|
|
||||||
<div class="card__image">
|
|
||||||
<img src="<%= product.imageUrl %>"
|
|
||||||
alt="<%= product.title %>">
|
|
||||||
</div>
|
|
||||||
<div class="card__content">
|
|
||||||
<h2 class="product__price">$<%= product.price %></h2>
|
|
||||||
<p class="product__description"><%= product.description %></p>
|
|
||||||
</div>
|
|
||||||
<div class="card__actions">
|
|
||||||
<a href="/products/<%= product._id %>" class="btn">Details</a>
|
|
||||||
<%- include('../includes/add-to-cart.ejs', {product: product}) %>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
<% } else { %>
|
|
||||||
<h1>No Products Found!</h1>
|
|
||||||
<% } %>
|
|
||||||
</main>
|
|
||||||
<%- include('../includes/end.ejs') %>
|
|
@ -1,25 +0,0 @@
|
|||||||
<%- include('../includes/head.ejs') %>
|
|
||||||
<link rel="stylesheet" href="/css/orders.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('../includes/navigation.ejs') %>
|
|
||||||
<main>
|
|
||||||
<% if (orders.length <= 0) { %>
|
|
||||||
<h1>Nothing there!</h1>
|
|
||||||
<% } else { %>
|
|
||||||
<ul class="orders">
|
|
||||||
<% orders.forEach(order => { %>
|
|
||||||
<li class="orders__item">
|
|
||||||
<h1>Order - # <%= order._id %></h1>
|
|
||||||
<ul class="orders__products">
|
|
||||||
<% order.products.forEach(p => { %>
|
|
||||||
<li class="orders__products-item"><%= p.product.title %> (<%= p.quantity %>)</li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
<% } %>
|
|
||||||
</main>
|
|
||||||
<%- include('../includes/end.ejs') %>
|
|
@ -1,16 +0,0 @@
|
|||||||
<%- include('../includes/head.ejs') %>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('../includes/navigation.ejs') %>
|
|
||||||
<main class="centered">
|
|
||||||
<h1><%= product.title %></h1>
|
|
||||||
<hr>
|
|
||||||
<div class="image">
|
|
||||||
<img src="<%= product.imageUrl %>" alt="<%= product.title %>">
|
|
||||||
</div>
|
|
||||||
<h2><%= product.price %></h2>
|
|
||||||
<p><%= product.description %></p>
|
|
||||||
<%- include('../includes/add-to-cart.ejs') %>
|
|
||||||
</main>
|
|
||||||
<%- include('../includes/end.ejs') %>
|
|
@ -1,40 +0,0 @@
|
|||||||
<%- include('../includes/head.ejs') %>
|
|
||||||
<link rel="stylesheet" href="/css/product.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%- include('../includes/navigation.ejs') %>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<% if (prods.length > 0) { %>
|
|
||||||
<div class="grid">
|
|
||||||
<% for (let product of prods) { %>
|
|
||||||
<article class="card product-item">
|
|
||||||
<header class="card__header">
|
|
||||||
<h1 class="product__title">
|
|
||||||
<%= product.title %>
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
<div class="card__image">
|
|
||||||
<img src="<%= product.imageUrl %>" alt="<%= product.title %>">
|
|
||||||
</div>
|
|
||||||
<div class="card__content">
|
|
||||||
<h2 class="product__price">$
|
|
||||||
<%= product.price %>
|
|
||||||
</h2>
|
|
||||||
<p class="product__description">
|
|
||||||
<%= product.description %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="card__actions">
|
|
||||||
<a href="/products/<%= product._id %>" class="btn">Details</a>
|
|
||||||
<%- include('../includes/add-to-cart.ejs', {product: product}) %>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
<% } else { %>
|
|
||||||
<h1>No Products Found!</h1>
|
|
||||||
<% } %>
|
|
||||||
</main>
|
|
||||||
<%- include('../includes/end.ejs') %>
|
|
Loading…
Reference in New Issue
Block a user