diff --git a/README.md b/README.md index 371b329..848faa1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,685 @@ The most hackable Web Shop! TODO -## Internal structure - -### Database +## Database ![database-erm](misc/images//database.png) + + +## Backend API endpoints + +The application host it's data in a SQLite database. The access is managed by an [ExpressJs](https://expressjs.com/) server which offers many REST-API endpoints for the frontend. The REST-API server runs on port 3000. + +### `/api` + +
+Server check +
+ +

Description

+Check if server is available + +

Request

+GET /api + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
---
+
+
+ + +
+Database reset +
+ +

Description

+Delete and refill the database with example values + +

Request

+GET /api/resetdatabase + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
---
+
+
+ +### `/categories` + +
+All categories +
+ +

Description

+Get all categories + +

Request

+GET /categories + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
---
+ +

Example Response

+ +```json +[ + { + "id": 0, + "name": "Electronic", + "icon": "mdi-chip", + "createdAt": "2024-09-13T07:51:40.118Z", + "updatedAt": "2024-09-13T07:51:40.118Z" + }, + { + "id": 1, + "name": "Sports", + "icon": "mdi-soccer", + "createdAt": "2024-09-13T07:51:40.118Z", + "updatedAt": "2024-09-13T07:51:40.118Z" + } +] +``` +
+
+ + +
+Add new category +
+ +

Description

+Add a new category + +

Request

+POST /categories + +
+ + +
+
+ + + + + + + + + + + + + + + +
Body ParametersRequired?Description
nameYesName of the category
iconYesMaterial Design Icon
+
+
+ + +
+Delete category +
+ +

Description

+Delete a category by it's id + +

Request

+DELETE /categories/:id + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
idYesDatabase ID of CategoryModel
+
+
+ +### `/products` + +
+All products +
+ +

Description

+Get all products + +

Request

+GET /products + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
---
+ +

Example Response

+ +```json +[ + { + "id": 0, + "brand": "Lenovo", + "name": "Thinkpad T14", + "categoryId": 0, + "price": 799.99, + "discount": 10, + "rating": 4.6, + "imageUrl": "thinkpad-t14s.jpg", + "description": "Die stabile Arbeitsmaschine. Mit AMD Ryzen 7 89029U, 128 GB RAM und 8 TB M.2 SSD!", + "createdAt": "2024-09-13T07:51:40.119Z", + "updatedAt": "2024-09-13T07:51:40.119Z", + "category": { + "id": 0, + "name": "Electronic", + "icon": "mdi-chip", + "createdAt": "2024-09-13T07:51:40.118Z", + "updatedAt": "2024-09-13T07:51:40.118Z" + } + } +] +``` +
+
+ + +
+Request one product +
+ +

Description

+Get a specific product by it's id + +

Request

+GET /products/:id + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
idYesID of the product in the database table
+ +

Example Response

+ +```json +{ + "id": 0, + "brand": "Lenovo", + "name": "Thinkpad T14", + "categoryId": 0, + "price": 799.99, + "discount": 10, + "rating": 4.6, + "imageUrl": "thinkpad-t14s.jpg", + "description": "Die stabile Arbeitsmaschine. Mit AMD Ryzen 7 89029U, 128 GB RAM und 8 TB M.2 SSD!", + "createdAt": "2024-09-13T07:51:40.119Z", + "updatedAt": "2024-09-13T07:51:40.119Z", + "category": { + "id": 0, + "name": "Electronic", + "icon": "mdi-chip", + "createdAt": "2024-09-13T07:51:40.118Z", + "updatedAt": "2024-09-13T07:51:40.118Z" + } +} +``` +
+
+ +
+Add new product +
+ +

Description

+Add a new product to the database + +

Request

+POST /products + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Body ParametersRequired?Description
brandYesBrand of the product
nameYesName of the product
descriptionNoDescription of the product
categoryIdYesID of a Category from database
priceNoName of the product
discountNoProcentual discount, 0 to 100
ratingNoProduct rating from 1 to 5
imageUrlNoName of the uploaded image file
+
+
+ + +
+Delete product +
+ +

Description

+Delete a product by it's id + +

Request

+DELETE /products/:id + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
idYesDatabase ID of ProductModel
+
+
+ + +### `/orders` + +
+Request orders of user +
+ +

Description

+Get all orders from a user + +

Request

+GET /orders/:id + +
+ + +
+
+ + + + + + + + + + +
ParameterRequired?Description
idYesID of the user in the database table
+ +

Example Response

+ +```json +[ + { + "id": 1, + "accountId": 3, + "totalPrice": 7.99, + "shippingProgress": 5, + "createdAt": "2024-09-09T12:24:24.225Z", + "updatedAt": "2024-09-13T07:51:40.120Z", + "orderItem": [ + { + "id": 1, + "orderId": 1, + "quantity": 1, + "productId": 6, + "createdAt": "2024-09-13T07:51:40.120Z", + "updatedAt": "2024-09-13T07:51:40.120Z", + "product": { + "id": 6, + "brand": "Aldous Huxley", + "name": "Brave New World", + "categoryId": 3, + "price": 7.99, + "discount": 0, + "rating": 4.4, + "imageUrl": "brave-new-world.jpg", + "description": "Brave New World beschreibt eine genormte Gesellschaft, in der Föten genetisch manipuliert und Menschen konditioniert werden. Ziel des Staates ist Zufriedenheit und Stabilität, und dies wird durch Gleichheit, Drogen und Propaganda erreicht. Gott und Religion...", + "createdAt": "2024-09-13T07:51:40.119Z", + "updatedAt": "2024-09-13T07:51:40.119Z" + } + } + ] + } +] +``` +
+
+ +
+Place a new order +
+ +

Description

+Place a new order to the database + +

Request

+POST /orders + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + +
Query ParametersRequired?Description
accountIdYesID of account who created this order
shippingProgressNoProgress of shipping, 1 to 5
orderItemYesList of ordered items (objects). Needs parameter quantity and productId
+
+
+ +### `/accounts` + +
+Login user +
+ +

Description

+Login process for user + +

Request

+GET /accounts + +
+ + +
+
+ + + + + + + + + + + + + + + +
Query ParametersRequired?Description
usernameYesName of user account
passwordYesPassword of user account
+ +

Example Response

+ +```json +{ + "loginSuccessful": true, + "userId": 3, + "message": "" +} +``` +
+
+ +
+Add an user +
+ +

Description

+Place a new account to the database + +

Request

+POST /accounts + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Body ParametersRequired?Description
usernameYesLogin username
passwordYesLogin password
firstNameNoFirst name of user
lastNameNoLast name of user
streetNoStreet where the user lives
houseNumberNoHouse number of user
postalCodeNoPostal code of users home
cityNoName of users city
+
+
+ +
+Update account +
+ +

Description

+Updating values of an existing account + +

Request

+PATCH /account + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Body ParametersRequired?Description
idYesIdentifier of dataset
usernameNoLogin username
passwordNoLogin password
firstNameNoFirst name of user
lastNameNoLast name of user
streetNoStreet where the user lives
houseNumberNoHouse number of user
postalCodeNoPostal code of users home
cityNoName of users city
+
+
+ + diff --git a/software/backend/models/account.model.ts b/software/backend/models/account.model.ts index b693ac0..174fcb0 100644 --- a/software/backend/models/account.model.ts +++ b/software/backend/models/account.model.ts @@ -11,19 +11,19 @@ export class Account extends Model { password: string @Column - firstName: string + firstName: string = "" @Column - lastName: string + lastName: string = "" @Column - street: string + street: string = "" @Column - houseNumber: number + houseNumber: number = 0 @Column - postalCode: number + postalCode: number = 0 @Column city: string diff --git a/software/backend/routes/account.routes.ts b/software/backend/routes/account.routes.ts index 658e165..841e585 100644 --- a/software/backend/routes/account.routes.ts +++ b/software/backend/routes/account.routes.ts @@ -4,57 +4,82 @@ import { validateString } from "../scripts/validateHelper"; export const account = Router() -// Request all user from the database -account.get("/", (req: Request, res: Response, next: NextFunction) => { - Account.findAll() - .then(accounts => { - res.json(accounts) - }) - .catch(next) +// Login user +account.get("/", (req: Request, res: Response) => { + Account.findOne({ + raw: true, + where: { username: req.body.username } + }) + .then(account => { + if (account != null) { + if (account.password == req.body.password) { + // Status: 200 Created + res.status(201).json({ + loginSuccessful: true, + userId: account.id, + message: "" + }).send() + } else { + // Status: 401 Unauthorized + res.status(401).json({ + loginSuccessful: false, + userId: -1, + message: "Wrong password" + }).send() + } + } else { + // Status: 401 Unauthorized + res.status(401).json({ + loginSuccessful: false, + userId: -1, + message: "Username doesn't exists" + }).send() + } + } + ) }) // Creating a new user -account.post("/register", (req: Request, res: Response, next: NextFunction) => { +account.post("/", (req: Request, res: Response) => { if (!validateString(req.body.username, 4)) { // Status: 400 Bad request - res.status(400).send({ error: "Username too short!" }) + res.status(400).json({ + message: "Username too short!" + }).send() } else if (!validateString(req.body.password, 8)) { // Status: 400 Bad request - res.status(400).send({ error: "Password too short!" }) + res.status(400).json({ + message: "Password too short!" + }).send() } else { Account.create(req.body) .then(account => { - res.json(account) - - // Status: 200 OK - res.status(200).send() + res.status(200).json(account).send() }).catch(reason => { // Status: 400 Bad request - res.status(400).send({ error: reason }) + res.status(400).json({ + message: reason + }).send() }) } }) -account.post("/login", (req: Request, res: Response, next: NextFunction) => { - Account.findOne({ raw: true, where: { username: req.body.username }}) +account.patch("/", (req: Request, res: Response) => { + Account.update(req.body, + { + where: { id: req.body.id } + }) .then(account => { - if (account != null) { - if (account.password == req.body.password) { - // Status: 200 OK - res.status(200).send({ userAccountId: account.id }) - } else { - // Status: 401 Unauthorized - res.status(401).send() - } - } else { - // Status: 401 Unauthorized - res.status(401).send() - } - } - ) -}) \ No newline at end of file + res.status(200).send() + }) + .catch(error => { + res.status(400).json({ + message: error + }).send() + }) +}) diff --git a/software/backend/routes/api.routes.ts b/software/backend/routes/api.routes.ts index 806391c..3259210 100644 --- a/software/backend/routes/api.routes.ts +++ b/software/backend/routes/api.routes.ts @@ -4,7 +4,7 @@ import { deleteAllTables, prepopulateDatabase } from '../scripts/databaseHelper' export const api = Router() api.get("/", (req: Request, res: Response, next: NextFunction) => { - res.send("Hello World!") + res.status(200).send() }) api.get("/resetdatabase", (req: Request, res: Response, next: NextFunction) => { diff --git a/software/backend/routes/category.routes.ts b/software/backend/routes/category.routes.ts index 0a3abd9..e941671 100644 --- a/software/backend/routes/category.routes.ts +++ b/software/backend/routes/category.routes.ts @@ -3,19 +3,41 @@ import { Category } from "../models/category.model"; export const category = Router() +// Get all categories category.get("/", (req: Request, res: Response, next: NextFunction) => { Category.findAll() .then(categories => { - res.json(categories) + res.status(200).json(categories).send() + }) + .catch(error => { + res.status(400).json({ message: error }).send() }) - .catch(next) }) +// Add new category category.post("/", (req: Request, res: Response, next: NextFunction) => { - try { - const category = Category.create(req.body) - res.status(201).json(category) - } catch (e) { - next(e) - } + Category.create(req.body) + .then(category => { + res.status(201).send() + }) + .catch(error => { + res.status(400).json({ + message: error + }).send() + }) +}) + +// Delete category +category.delete("/:id", (req: Request, res: Response, next: NextFunction) => { + Category.destroy({ + where: { id: req.params.id } + }) + .then(category => { + res.status(200).send() + }) + .catch(error => { + res.status(406).json({ + message: error + }).send() + }) }) \ No newline at end of file diff --git a/software/backend/routes/order.routes.ts b/software/backend/routes/order.routes.ts index dc27bb2..053b2a7 100644 --- a/software/backend/routes/order.routes.ts +++ b/software/backend/routes/order.routes.ts @@ -1,18 +1,53 @@ -import { Router, Request, Response, NextFunction } from "express"; +import { Router, Request, Response } from "express"; import { Order } from "../models/order.model"; import { Product } from "../models/product.model"; import { OrderItem } from "../models/orderItem.model"; export const order = Router() -order.get("/", (req: Request, res: Response, next: NextFunction) => { +// Get all orders of one account by it's user id +order.get("/:id", (req: Request, res: Response) => { Order.findAll({ - where: { accountId: req.query.accountId }, + where: { accountId: req.params.id }, include: [ { model: OrderItem, include: [ Product ] } ] }) .then(orders => { - res.send(orders) + res.status(200).send(orders) }) +}) + +// Place a new order +order.post("/", (req: Request, res: Response) => { + let totalPrice = 0 + + Order.create(req.body) + .then(order => { + + for (let orderItem of req.body.orderItem) { + OrderItem.create({ + "orderId": order.id, + "quantity": orderItem.quantity, + "productId": orderItem.productId + }) + + Product.findOne({ + raw: true, + where: { id: orderItem.productId } + }) + .then(product => { + totalPrice += product.price * orderItem.quantity + + Order.update({ + totalPrice: totalPrice + }, { + where: { id: order.id }, + }) + }) + } + }) + + // Created + res.status(201).send() }) \ No newline at end of file diff --git a/software/backend/routes/orderItem.routes.ts b/software/backend/routes/orderItem.routes.ts deleted file mode 100644 index d37a3b4..0000000 --- a/software/backend/routes/orderItem.routes.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Product } from "../models/product.model"; -import { OrderItem } from "../models/orderItem.model"; -import { Router, Request, Response, NextFunction } from "express"; - -export const orderItem = Router() - diff --git a/software/backend/routes/product.routes.ts b/software/backend/routes/product.routes.ts index 7d42370..e112b75 100644 --- a/software/backend/routes/product.routes.ts +++ b/software/backend/routes/product.routes.ts @@ -1,19 +1,51 @@ import { Router, Request, Response, NextFunction } from "express"; import { Product } from "../models/product.model"; +import { Category } from "../models/category.model"; export const product = Router() -product.get("/", (req: Request, res: Response, next: NextFunction) => { - Product.findAll() +// Get all products +product.get("/", (req: Request, res: Response) => { + Product.findAll({ + include: [ Category ] + }) .then(products => { - res.json(products) + res.status(200).json(products) }) - .catch(next) }) -product.get("/:productId", (req: Request, res: Response, next: NextFunction) => { +// Get a product by id +product.get("/:productId", (req: Request, res: Response) => { Product.findByPk(req.params.productId) .then(product => { - res.json(product) + res.status(200).json(product) + }) +}) + +// Add a new product +product.post("/", (req: Request, res: Response) => { + Product.create(req.body) + .then(product => { + res.status(200).send() + }) + .catch(error => { + res.status(400).json({ + message: error + }).send() + }) +}) + +// Remove a product +product.delete("/:id", (req: Request, res: Response) => { + Product.destroy({ + where: { id: req.params.id } + }) + .then(product => { + res.status(200).send() + }) + .catch(error => { + res.status(406).json({ + message: error + }).send() }) }) \ No newline at end of file diff --git a/software/backend/server.ts b/software/backend/server.ts index a420b00..e258bd4 100644 --- a/software/backend/server.ts +++ b/software/backend/server.ts @@ -7,7 +7,6 @@ import { category } from './routes/category.routes' import { product } from './routes/product.routes' import { order } from './routes/order.routes' import { account } from './routes/account.routes' -import { orderItem } from './routes/orderItem.routes' const app = express() const port = 3000 @@ -27,7 +26,6 @@ app.use("/categories", category) app.use("/products", product) app.use("/orders", order) app.use("/accounts", account) -app.use("/orderItems", orderItem) // Static files const path = require('path') diff --git a/software/src/data/models/categoryModel.ts b/software/src/data/models/categoryModel.ts index a2cbabe..1829b21 100644 --- a/software/src/data/models/categoryModel.ts +++ b/software/src/data/models/categoryModel.ts @@ -1,7 +1,7 @@ export class CategoryModel { id: number = -1 - name: string = "All" - icon: string = "mdi-all-inclusive" + name: string + icon: string createdAt: string = "" updatedAt: string = "" } \ No newline at end of file diff --git a/software/src/data/models/productModel.ts b/software/src/data/models/productModel.ts index 7f266b9..b99113a 100644 --- a/software/src/data/models/productModel.ts +++ b/software/src/data/models/productModel.ts @@ -1,9 +1,9 @@ export class ProductModel { id: number = -1 - brand: string = "" - name: string = "" + brand: string + name: string description: string = "" - categoryId: number = 0 + categoryId: number price: number = 0 discount: number = 0 rating: number = 1 diff --git a/software/src/locales/de.json b/software/src/locales/de.json index 27c700f..3465ae8 100644 --- a/software/src/locales/de.json +++ b/software/src/locales/de.json @@ -22,7 +22,7 @@ "product": { "product": "Produkt", "products": "Produkte", - "productName": "Product Name", + "productName": "Produkt Name", "brand": "Marke", "productPrice": "Einzelpreis", "category": "Kategorie" diff --git a/software/src/pages/accountPage/index.vue b/software/src/pages/accountPage/index.vue index f9eec71..a03c718 100644 --- a/software/src/pages/accountPage/index.vue +++ b/software/src/pages/accountPage/index.vue @@ -1,6 +1,25 @@ \ No newline at end of file