feat: day 29
This commit is contained in:
48
webseite/backend/api.rest
Normal file
48
webseite/backend/api.rest
Normal file
@@ -0,0 +1,48 @@
|
||||
# BROWSE Products
|
||||
|
||||
GET http://127.0.0.1:8000/api/products
|
||||
|
||||
###
|
||||
|
||||
# READ Product
|
||||
|
||||
GET http://127.0.0.1:8000/api/products/c38913d4-9524-461f-85d2-525241166e8e
|
||||
|
||||
|
||||
###
|
||||
|
||||
# ADD Product
|
||||
|
||||
POST http://127.0.0.1:8000/api/products
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Testproduct",
|
||||
"price": 9.99
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# UPDATE Product
|
||||
|
||||
PUT http://127.0.0.1:8000/api/products
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"_id": "940c316b-518c-4a18-b1f6-328e010eef4d",
|
||||
"name": "Testproduct Updated",
|
||||
"price": 123.22
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# DELETE Product
|
||||
|
||||
DELETE http://127.0.0.1:8000/api/products/940c316b-518c-4a18-b1f6-328e010eef4d
|
||||
Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,5 +22,17 @@
|
||||
"position": 4,
|
||||
"name": "16-Bit Legendary Hero Heat-Change Mug",
|
||||
"price": 12.99
|
||||
},
|
||||
{
|
||||
"_id": "3b3352c1-789d-4b4f-8a93-f05f44ece5a5",
|
||||
"position": 5,
|
||||
"name": "32-Bit Legendary Hero Heat-Change Mug",
|
||||
"price": 18.99
|
||||
},
|
||||
{
|
||||
"_id": "5af39d33-cbcd-42c7-a160-50084faa91ba",
|
||||
"position": 6,
|
||||
"name": "64-Bit Legendary Hero Heat-Change Mug",
|
||||
"price": 21.99
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from 'node:fs';
|
||||
import { v4 as id } from 'uuid';
|
||||
|
||||
// Factoryfunction
|
||||
const ProductModel = () => {
|
||||
@@ -13,6 +14,14 @@ const ProductModel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
try {
|
||||
fs.writeFileSync('./data/products.json', JSON.stringify(products, null, 2), 'utf-8');
|
||||
} catch (error) {
|
||||
console.log('Something went wrong:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getAll = () => {
|
||||
return products;
|
||||
};
|
||||
@@ -28,11 +37,66 @@ const ProductModel = () => {
|
||||
return product;
|
||||
};
|
||||
|
||||
const add = (product) => {
|
||||
const { name, price } = product;
|
||||
|
||||
if (!hasAllProps(product)) return false;
|
||||
|
||||
products.push({
|
||||
_id: id(),
|
||||
name,
|
||||
price: Number(price.toFixed(2)),
|
||||
position: products.length + 1,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const update = (product) => {
|
||||
const foundIdx = products.findIndex((obj) => {
|
||||
return obj._id === product._id;
|
||||
});
|
||||
|
||||
// Guard
|
||||
if (foundIdx === -1 || !hasAllProps(product)) return false;
|
||||
|
||||
products[foundIdx] = product;
|
||||
return true;
|
||||
};
|
||||
|
||||
const remove = (id) => {
|
||||
const foundIdx = products.findIndex((obj) => {
|
||||
return obj._id === id;
|
||||
});
|
||||
|
||||
// Guard
|
||||
if (foundIdx === -1) return false;
|
||||
|
||||
products.splice(foundIdx, 1);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* hasAllProps - Überprüfung ob Product "name" und "price"
|
||||
* @return boolean
|
||||
**/
|
||||
const hasAllProps = (product) => {
|
||||
// return Object.hasOwn(product, 'name') && Object.hasOwn(product, 'price');
|
||||
|
||||
const keys = Object.keys(product); // => ['name', 'price']
|
||||
return keys.includes('name') && keys.includes('price');
|
||||
};
|
||||
|
||||
return {
|
||||
// load: load
|
||||
load,
|
||||
save,
|
||||
add,
|
||||
getAll,
|
||||
get,
|
||||
update,
|
||||
delete: remove,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
16
webseite/backend/package-lock.json
generated
16
webseite/backend/package-lock.json
generated
@@ -12,7 +12,8 @@
|
||||
"chalk": "^5.6.2",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1"
|
||||
"express": "^5.2.1",
|
||||
"uuid": "^14.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
@@ -896,6 +897,19 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz",
|
||||
"integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist-node/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"chalk": "^5.6.2",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1"
|
||||
"express": "^5.2.1",
|
||||
"uuid": "^14.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ products.load();
|
||||
|
||||
// Middleware
|
||||
app.use(cors(corsOptions)); // Fängt alle HTTP-Requests vor den Routenabfragen ab.
|
||||
app.use(express.json()); // HTTP-Request Body wird als JSON Objekt erwartet und geparsed wird.
|
||||
|
||||
// Routes
|
||||
app.get('/', (req, res) => {
|
||||
@@ -43,11 +44,78 @@ app.get('/persons', (req, res) => {
|
||||
res.send(persons);
|
||||
});
|
||||
|
||||
// REST API ======
|
||||
// Representational State Transfer (abgekürzt REST)
|
||||
// GET - Daten Holen
|
||||
// POST - Daten hinzufügen
|
||||
// PUT/PATCH - Daten aktualisieren / Einen Datensatz aktualisieren
|
||||
// DELETE - Daten löschen
|
||||
|
||||
// HTTP - Methode - POST
|
||||
// (C)RUD - create - neues Produkt erstellen
|
||||
// BRE(A)D - add
|
||||
app.post('/api/products', (req, res) => {
|
||||
const product = req.body;
|
||||
|
||||
if (!products.add(product)) {
|
||||
return res.status(400).send({ msg: 'Could not add product', status: 400, success: false });
|
||||
}
|
||||
|
||||
res.send({ msg: 'Product added', status: 200, success: true, data: product });
|
||||
});
|
||||
|
||||
// HTTP - Methode - GET
|
||||
// C(R)UD - read
|
||||
// (B)READ - browse
|
||||
app.get('/api/products', (req, res) => {
|
||||
const data = products.getAll();
|
||||
res.send(data);
|
||||
});
|
||||
|
||||
// HTTP - Methode - GET
|
||||
// C(R)UD - read
|
||||
// B(R)EAD - read
|
||||
app.get('/api/products/:id', (req, res) => {
|
||||
const id = req.params.id; // routenparameter :id wird als Eigenschaft in params-Objekt abgelegt
|
||||
const product = products.get(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(400).send({ msg: 'Could not get product', status: 400, success: false });
|
||||
}
|
||||
|
||||
res.send(product);
|
||||
});
|
||||
|
||||
// HTTP - Methode - PUT/PATCH
|
||||
// CR(U)D - update
|
||||
// BR(E)AD - edit
|
||||
app.put('/api/products', (req, res) => {
|
||||
const product = req.body;
|
||||
|
||||
const updated = products.update(product);
|
||||
|
||||
if (!updated) {
|
||||
return res.status(400).send({ msg: 'Could not update product', status: 400, success: false });
|
||||
}
|
||||
|
||||
res.send({ msg: 'Product updated', status: 200, success: true, data: product });
|
||||
});
|
||||
|
||||
// HTTP - Methode - DELETE
|
||||
// CRU(D) - delete
|
||||
// BREA(D) - delete
|
||||
app.delete('/api/products/:id', (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
if (!products.delete(id)) {
|
||||
return res.status(400).send({ msg: 'Could not delete product', status: 400, success: false });
|
||||
}
|
||||
|
||||
res.send({ msg: 'Product deleted', status: 200, success: true, data: id });
|
||||
});
|
||||
|
||||
// =========
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
console.log(color.magenta(`🚀 Server is running at: ${BASE_URL}`));
|
||||
console.log(color.yellow('CTRL + C to close.'));
|
||||
|
||||
@@ -5166,6 +5166,7 @@
|
||||
|
||||
// dev/scripts/modules/ProductManager.js
|
||||
var ProductManager = (el = null) => {
|
||||
const BASE_URL = "http://127.0.0.1:8000";
|
||||
const module = el || document.querySelector(".product-manager") || console.error("Product Manager not found");
|
||||
if (!module) return;
|
||||
const DOM = {
|
||||
@@ -5176,7 +5177,12 @@
|
||||
inputPrice: module.querySelector(".input-product-price"),
|
||||
btnAdd: module.querySelector(".button-product-add"),
|
||||
templateRow: module.querySelector(".template-row"),
|
||||
modalEdit: module.querySelector(".modal-edit")
|
||||
// Modal
|
||||
modalEdit: module.querySelector(".modal-edit"),
|
||||
inputEditName: module.querySelector(".input-edit-name"),
|
||||
inputEditPrice: module.querySelector(".input-edit-price"),
|
||||
inputEditId: module.querySelector(".input-edit-id"),
|
||||
inputEditPosition: module.querySelector(".input-edit-position")
|
||||
};
|
||||
const bsModalEdit = new Modal(DOM.modalEdit, {
|
||||
backdrop: "static",
|
||||
@@ -5198,6 +5204,14 @@
|
||||
addProduct(product);
|
||||
disableNonFunctionalButtons();
|
||||
};
|
||||
const onClickEdit = (e) => {
|
||||
const btnEl = e.currentTarget;
|
||||
const currentRow = parents(btnEl, "tr")[0];
|
||||
const id = currentRow.dataset.id;
|
||||
getProduct(id).then((data) => {
|
||||
showModalEdit(data);
|
||||
});
|
||||
};
|
||||
const onClickRemove = (e) => {
|
||||
const btnEl = e.currentTarget;
|
||||
const currentRowEl = parents(btnEl, "tr")[0];
|
||||
@@ -5256,7 +5270,20 @@
|
||||
};
|
||||
const fetchProducts = async () => {
|
||||
try {
|
||||
const response = await fetch("http://127.0.0.1:8000/api/products");
|
||||
const response = await fetch(`${BASE_URL}/api/products`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Fetch went wrong.");
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return error;
|
||||
}
|
||||
};
|
||||
const getProduct = async (id) => {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/api/products/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Fetch went wrong.");
|
||||
}
|
||||
@@ -5276,7 +5303,7 @@
|
||||
});
|
||||
};
|
||||
const addProduct = (product) => {
|
||||
const { name, price } = product;
|
||||
const { name, price, _id: id, position } = product;
|
||||
const trEl = DOM.templateRow.content.firstElementChild.cloneNode(true);
|
||||
const tdNameEl = trEl.querySelector(".td-name");
|
||||
const tdPriceEl = trEl.querySelector(".td-price");
|
||||
@@ -5284,8 +5311,10 @@
|
||||
const btnMoveUpEl = trEl.querySelector(".button-product-move-up");
|
||||
const btnMoveDownEl = trEl.querySelector(".button-product-move-down");
|
||||
const btnDragEl = trEl.querySelector(".button-drag");
|
||||
const btnEditEl = trEl.querySelector(".button-product-edit");
|
||||
tdNameEl.textContent = name;
|
||||
tdPriceEl.textContent = price;
|
||||
btnEditEl.addEventListener("click", onClickEdit);
|
||||
btnRemoveEl.addEventListener("click", onClickRemove);
|
||||
btnMoveUpEl.addEventListener("click", onClickMoveUp);
|
||||
btnMoveDownEl.addEventListener("click", onClickMoveDown);
|
||||
@@ -5294,8 +5323,17 @@
|
||||
trEl.addEventListener("dragstart", onDragStart);
|
||||
trEl.addEventListener("dragover", onDragOver);
|
||||
trEl.addEventListener("drop", onDrop);
|
||||
trEl.dataset.id = id;
|
||||
trEl.dataset.position = position;
|
||||
DOM.tBody.appendChild(trEl);
|
||||
};
|
||||
const showModalEdit = (product) => {
|
||||
const { name = "", price = 0, _id: id, position } = product;
|
||||
DOM.inputEditName.value = name;
|
||||
DOM.inputEditPrice.value = price;
|
||||
DOM.inputEditId.value = id;
|
||||
DOM.inputEditPosition.value = position;
|
||||
};
|
||||
const disableNonFunctionalButtons = () => {
|
||||
Array.from(DOM.tBody.querySelectorAll("tr")).forEach((tr) => {
|
||||
const btnMoveUp = tr.querySelector(".button-product-move-up");
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,8 @@ import { Modal } from 'bootstrap'; // ohne Pfadangabe (Modul auslesen aus node_m
|
||||
const ProductManager = (el = null) => {
|
||||
// === DOM & VARS =======
|
||||
|
||||
const BASE_URL = 'http://127.0.0.1:8000';
|
||||
|
||||
const module = el || document.querySelector('.product-manager') || console.error('Product Manager not found');
|
||||
|
||||
if (!module) return;
|
||||
@@ -20,7 +22,12 @@ const ProductManager = (el = null) => {
|
||||
btnAdd: module.querySelector('.button-product-add'),
|
||||
templateRow: module.querySelector('.template-row'),
|
||||
|
||||
// Modal
|
||||
modalEdit: module.querySelector('.modal-edit'),
|
||||
inputEditName: module.querySelector('.input-edit-name'),
|
||||
inputEditPrice: module.querySelector('.input-edit-price'),
|
||||
inputEditId: module.querySelector('.input-edit-id'),
|
||||
inputEditPosition: module.querySelector('.input-edit-position'),
|
||||
};
|
||||
|
||||
const bsModalEdit = new Modal(DOM.modalEdit, {
|
||||
@@ -54,6 +61,18 @@ const ProductManager = (el = null) => {
|
||||
disableNonFunctionalButtons();
|
||||
};
|
||||
|
||||
const onClickEdit = (e) => {
|
||||
const btnEl = e.currentTarget;
|
||||
const currentRow = parents(btnEl, 'tr')[0];
|
||||
const id = currentRow.dataset.id;
|
||||
|
||||
// async fetch
|
||||
getProduct(id).then((data) => {
|
||||
// console.log(data);
|
||||
showModalEdit(data);
|
||||
});
|
||||
};
|
||||
|
||||
const onClickRemove = (e) => {
|
||||
const btnEl = e.currentTarget;
|
||||
//const currentRowEl = btnEl.parentNode.parentNode; // parentNode -> td - parentNode -> tr
|
||||
@@ -103,6 +122,7 @@ const ProductManager = (el = null) => {
|
||||
const currentRowEl = parents(btnEl, 'tr')[0];
|
||||
currentRowEl.draggable = true;
|
||||
};
|
||||
|
||||
const onMouseUpDrag = (e) => {
|
||||
const btnEl = e.currentTarget;
|
||||
const currentRowEl = parents(btnEl, 'tr')[0];
|
||||
@@ -150,9 +170,31 @@ const ProductManager = (el = null) => {
|
||||
};
|
||||
|
||||
// === XHR/FETCH ========
|
||||
|
||||
// HTTP - Methode - GET
|
||||
// C(R)UD - Read
|
||||
// (B)READ - Browse - auslesen aller Produkte
|
||||
const fetchProducts = async () => {
|
||||
try {
|
||||
const response = await fetch('http://127.0.0.1:8000/api/products'); // HTTP Request URL
|
||||
const response = await fetch(`${BASE_URL}/api/products`); // HTTP Request URL
|
||||
if (!response.ok) {
|
||||
throw new Error('Fetch went wrong.');
|
||||
}
|
||||
const data = await response.json(); // HTTP-Response
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
// HTTP - Methode - GET
|
||||
// C(R)UD - Read
|
||||
// B(R)EAD - Read - auslesen eines Produkts
|
||||
const getProduct = async (id) => {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/api/products/${id}`); // HTTP Request URL
|
||||
if (!response.ok) {
|
||||
throw new Error('Fetch went wrong.');
|
||||
}
|
||||
@@ -183,7 +225,7 @@ const ProductManager = (el = null) => {
|
||||
};
|
||||
|
||||
const addProduct = (product) => {
|
||||
const { name, price } = product;
|
||||
const { name, price, _id: id, position } = product;
|
||||
|
||||
// https://developer.mozilla.org/de/docs/Web/API/HTMLTemplateElement/content
|
||||
const trEl = DOM.templateRow.content.firstElementChild.cloneNode(true);
|
||||
@@ -195,10 +237,12 @@ const ProductManager = (el = null) => {
|
||||
const btnMoveDownEl = trEl.querySelector('.button-product-move-down');
|
||||
|
||||
const btnDragEl = trEl.querySelector('.button-drag');
|
||||
const btnEditEl = trEl.querySelector('.button-product-edit');
|
||||
|
||||
tdNameEl.textContent = name;
|
||||
tdPriceEl.textContent = price;
|
||||
|
||||
btnEditEl.addEventListener('click', onClickEdit);
|
||||
btnRemoveEl.addEventListener('click', onClickRemove);
|
||||
btnMoveUpEl.addEventListener('click', onClickMoveUp);
|
||||
btnMoveDownEl.addEventListener('click', onClickMoveDown);
|
||||
@@ -211,9 +255,22 @@ const ProductManager = (el = null) => {
|
||||
trEl.addEventListener('dragover', onDragOver);
|
||||
trEl.addEventListener('drop', onDrop);
|
||||
|
||||
trEl.dataset.id = id; // <tr data-id="..." >...</tr>
|
||||
trEl.dataset.position = position; // <tr data-position="..." >...</tr>
|
||||
|
||||
DOM.tBody.appendChild(trEl); // Im DOM hinzufügen
|
||||
};
|
||||
|
||||
const showModalEdit = (product) => {
|
||||
const { name = '', price = 0, _id: id, position } = product;
|
||||
|
||||
DOM.inputEditName.value = name;
|
||||
DOM.inputEditPrice.value = price;
|
||||
|
||||
DOM.inputEditId.value = id;
|
||||
DOM.inputEditPosition.value = position;
|
||||
};
|
||||
|
||||
const disableNonFunctionalButtons = () => {
|
||||
Array.from(DOM.tBody.querySelectorAll('tr')).forEach((tr) => {
|
||||
const btnMoveUp = tr.querySelector('.button-product-move-up');
|
||||
|
||||
@@ -168,6 +168,8 @@
|
||||
class="form-control input-edit-price" />
|
||||
<span class="input-group-text">€</span>
|
||||
</div>
|
||||
<input type="hidden" name="id" class="input-edit-id" />
|
||||
<input type="hidden" name="position" class="input-edit-position" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user