From 84fa2e0ab0d3b4eebe9f44b91f92b864afa74462 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 8 Jul 2026 10:54:07 +0200 Subject: [PATCH] refactor: change backend to typescript and update types and variables accordingly --- .gitignore | 2 + backend/misc/express.d.ts | 11 + backend/misc/types.ts | 8 + backend/package.json | 13 +- .../routes/app/database/products.database.ts | 278 ++++--- .../routes/app/database/storage.database.ts | 110 ++- backend/routes/app/products.route.ts | 368 ++++++--- backend/routes/app/storage.route.ts | 225 +++-- backend/routes/app/users.route.ts | 170 +++- backend/server.js | 138 ---- backend/server.ts | 147 ++++ backend/services/helperFuncs.ts | 15 +- backend/services/tokenService.ts | 45 +- backend/tsconfig.json | 45 + backend/vite.config.ts | 32 + eslint.config.mjs | 5 + frontend/README.md | 50 +- frontend/tsconfig.json | 8 +- package-lock.json | 779 +++++++++++++++++- package.json | 9 +- shared/package.json | 17 + shared/src/error/errorcodes.enum.ts | 39 +- shared/tsconfig.json | 2 + shared/vite.config.ts | 10 +- 24 files changed, 1911 insertions(+), 615 deletions(-) create mode 100644 backend/misc/express.d.ts delete mode 100644 backend/server.js create mode 100644 backend/server.ts create mode 100644 backend/tsconfig.json diff --git a/.gitignore b/.gitignore index c826165..c9df4d7 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ Temporary Items ToDo.txt .idea +dist +index.ts .env .docker/volumes diff --git a/backend/misc/express.d.ts b/backend/misc/express.d.ts new file mode 100644 index 0000000..39d000e --- /dev/null +++ b/backend/misc/express.d.ts @@ -0,0 +1,11 @@ +import type {AuthTokenPayload} from "../services/tokenService.js"; + +declare global { + namespace Express { + interface Request { + user?: AuthTokenPayload; + } + } +} + +export {}; diff --git a/backend/misc/types.ts b/backend/misc/types.ts index e69de29..a9ed413 100644 --- a/backend/misc/types.ts +++ b/backend/misc/types.ts @@ -0,0 +1,8 @@ +export type ErrorCode = readonly [code: string, message: string]; + +export interface Response { + success: boolean; + code: string; + data: any; + message: string; +} \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 1bd0184..7b80d08 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,22 +2,27 @@ "name": "backend", "version": "0.0.0-dev", "description": "", - "main": "server.js", + "main": "server.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js", - "dev": "node --watch server.js" + "start": "node server.ts", + "dev": "node --watch server.ts" }, "keywords": [], "author": "", "license": "ISC", "type": "module", "dependencies": { + "@stockhome/shared": "1.0.0", "cors": "^2.8.5", "dotenv": "^17.2.3", "ejs": "^3.1.10", "express": "^5.2.1", "jose": "^6.0.12", "mysql2": "^3.16.0" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^26.1.0" } -} \ No newline at end of file +} diff --git a/backend/routes/app/database/products.database.ts b/backend/routes/app/database/products.database.ts index 48be2db..b724c33 100644 --- a/backend/routes/app/database/products.database.ts +++ b/backend/routes/app/database/products.database.ts @@ -1,151 +1,189 @@ -import mysql from "mysql2"; +import mysql, {type ResultSetHeader, type RowDataPacket} from "mysql2/promise"; import dotenv from "dotenv"; -import {PRODUCT_ERROR_CODE} from "@stockhome/shared"; import {returnErrorCode} from "../../../services/helperFuncs.js"; +import {PRODUCT_ERROR_CODE} from "@stockhome/shared"; dotenv.config(); -const pool = mysql - .createPool({ - host: process.env.DB_HOST, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - }) - .promise(); +const pool = mysql.createPool({ + host: process.env.DB_HOST!, + user: process.env.DB_USER!, + password: process.env.DB_PASSWORD!, + database: process.env.DB_NAME!, +}); export const newProduct = async ( - name, - description, - price, - amount, - storage_location, - expiry_date, - bottling_date, -) => { - const newPrice = price !== "" ? price : null; + name: string, + description: string, + price: string, + amount: string, + storage_location: string, + expiry_date: string, + bottling_date: string, +): Promise => { + const newPrice = price !== "" ? price : null; - const [result] = await pool.query( - "INSERT INTO products (name, description, price, amount, storage_location, expiry_date, bottling_date) VALUES (?, ?, ?, ?, UUID_TO_BIN(?), ?, ?)", - [ - name, - description, - newPrice, - amount, - storage_location, - expiry_date, - bottling_date, - ], - ); + const [result] = await pool.query( + "INSERT INTO products (name, description, price, amount, storage_location, expiry_date, bottling_date) VALUES (?, ?, ?, ?, UUID_TO_BIN(?), ?, ?)", + [name, description, newPrice, amount, storage_location, expiry_date, bottling_date], + ); - if (result.affectedRows > 0) { - return { code: "sp001" }; - } + if (result.affectedRows > 0) { + return { + success: true, + code: "SP001", + data: null, + message: "New Product successfully created.", + }; + } - return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_CREATED); + return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_CREATED); }; -export const productDetails = async (uuid) => { - const [result] = await pool.query( - `SELECT - BIN_TO_UUID(p.uuid) AS uuid, - p.name, - p.description, - p.price, - p.amount, - BIN_TO_UUID(s.uuid) AS storage_location_uuid, - s.name AS storage_location_name, - p.expiry_date, - p.bottling_date, - p.picture - FROM products p - JOIN storage_locations s ON p.storage_location = s.uuid - WHERE p.uuid = UUID_TO_BIN(?)`, - [uuid], - ); +export const productDetails = async (uuid: string) => { + const [result] = await pool.query( + `SELECT BIN_TO_UUID(p.uuid) AS uuid, + p.name, + p.description, + p.price, + p.amount, + BIN_TO_UUID(s.uuid) AS storage_location_uuid, + s.name AS storage_location_name, + p.expiry_date, + p.bottling_date, + p.picture + FROM products p + JOIN storage_locations s ON p.storage_location = s.uuid + WHERE p.uuid = UUID_TO_BIN(?)`, + [uuid], + ); - if (result.length > 0) { - return { code: "sp003", data: result[0] }; - } + if (result.length > 0) { + return { + success: true, + code: "SP003", + data: result[0], + message: "Product details successfully loaded.", + }; + } - return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_FOUND); + return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_FOUND); }; export const allProducts = async () => { - const [result] = await pool.query(` - SELECT - BIN_TO_UUID(p.uuid) AS uuid, - p.name, - p.description, - p.price, - p.amount, - BIN_TO_UUID(s.uuid) AS storage_location_uuid, - s.name AS storage_location_name, - p.expiry_date, - p.bottling_date, - p.picture, - p.created_at, - p.updated_at - FROM products p - JOIN storage_locations s ON p.storage_location = s.uuid - WHERE p.deleted = 0 - `); + const [result] = await pool.query(` + SELECT BIN_TO_UUID(p.uuid) AS uuid, + p.name, + p.description, + p.price, + p.amount, + BIN_TO_UUID(s.uuid) AS storage_location_uuid, + s.name AS storage_location_name, + p.expiry_date, + p.bottling_date, + p.picture, + p.created_at, + p.updated_at + FROM products p + JOIN storage_locations s ON p.storage_location = s.uuid + WHERE p.deleted = 0 + `); - if (result.length > 0) { - return { code: "sp002", data: result }; - } + if (result.length > 0) { + return { + success: true, + code: "SP002", + data: result, + message: "All products successfully loaded.", + }; + } - return returnErrorCode(PRODUCT_ERROR_CODE.NO_PRODUCTS_FOUND); + return returnErrorCode(PRODUCT_ERROR_CODE.NO_PRODUCTS_FOUND); }; -export const setAmount = async (itemUUID, amount) => { - const [result] = await pool.query( - ` - UPDATE products SET amount = ? WHERE uuid = UUID_TO_BIN(?) - `, - [amount, itemUUID], - ); +export const setAmount = async (itemUUID: string, amount: number) => { + const [result] = await pool.query( + `UPDATE products + SET amount = ? + WHERE uuid = UUID_TO_BIN(?)`, + [amount, itemUUID], + ); - if (result.affectedRows > 0) { - return { code: "sp004" }; - } + if (result.affectedRows > 0) { + return { + success: true, + code: "SP004", + data: null, + message: "Amount successfully updated.", + }; + } - return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_AMOUNT_NOT_UPDATED); + return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_AMOUNT_NOT_UPDATED); }; -export const updateItem = async (itemUUID, newValues) => { - const [result] = await pool.query( - ` - UPDATE products SET name = ?, description = ?, price = ?, amount = ?, storage_location = UUID_TO_BIN(?), expiry_date = ?, bottling_date = ? WHERE uuid = UUID_TO_BIN(?); - `, - [ - newValues.name, - newValues.description, - newValues.price, - newValues.amount, - newValues.storage_location_uuid, - newValues.expiry_date, - newValues.bottling_date, - itemUUID, - ], - ); +export const updateItem = async ( + itemUUID: string, + newValues: { + name: string; + description: string; + price: string; + amount: string; + storage_location_uuid: string; + expiry_date: string; + bottling_date: string; + }, +) => { + const [result] = await pool.query( + `UPDATE products + SET name = ?, + description = ?, + price = ?, + amount = ?, + storage_location = UUID_TO_BIN(?), + expiry_date = ?, + bottling_date = ? + WHERE uuid = UUID_TO_BIN(?);`, + [ + newValues.name, + newValues.description, + newValues.price, + newValues.amount, + newValues.storage_location_uuid, + newValues.expiry_date, + newValues.bottling_date, + itemUUID, + ], + ); - if (result.affectedRows > 0) { - return { code: "sp005" }; - } + if (result.affectedRows > 0) { + return { + success: true, + code: "SP005", + data: null, + message: "Item updated successfully.", + }; + } - return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_UPDATED); + return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_UPDATED); }; -export const deleteProduct = async (uuid) => { - const [result] = await pool.query( - `UPDATE products SET deleted = 1 WHERE uuid = UUID_TO_BIN(?);`, - [uuid], - ); +export const deleteProduct = async (uuid: string) => { + const [result] = await pool.query( + `UPDATE products + SET deleted = 1 + WHERE uuid = UUID_TO_BIN(?);`, + [uuid], + ); - if (result.affectedRows > 0) { - return { code: "sp006" }; - } + if (result.affectedRows > 0) { + return { + success: true, + code: "SP006", + data: null, + message: "Product deleted successfully.", + }; + ; + } - return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED); -}; + return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED); +}; \ No newline at end of file diff --git a/backend/routes/app/database/storage.database.ts b/backend/routes/app/database/storage.database.ts index 72e0ab4..952e33b 100644 --- a/backend/routes/app/database/storage.database.ts +++ b/backend/routes/app/database/storage.database.ts @@ -1,65 +1,87 @@ -import mysql from "mysql2"; +import mysql, {type ResultSetHeader, type RowDataPacket} from "mysql2/promise"; import dotenv from "dotenv"; import {STORAGE_ERROR_CODE} from "@stockhome/shared"; import {returnErrorCode} from "../../../services/helperFuncs.js"; + dotenv.config(); -const pool = mysql - .createPool({ - host: process.env.DB_HOST, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - }) - .promise(); +const pool = mysql.createPool({ + host: process.env.DB_HOST!, + user: process.env.DB_USER!, + password: process.env.DB_PASSWORD!, + database: process.env.DB_NAME!, +}); export const allStorages = async () => { - const [result] = await pool.query( - "SELECT BIN_TO_UUID(uuid) AS uuid, name, description, created_at, updated_at FROM storage_locations;", - ); + const [result] = await pool.query( + "SELECT BIN_TO_UUID(uuid) AS uuid, name, description, created_at, updated_at FROM storage_locations;", + ); - if (result.length > 0) { - return { code: "ss001", data: result }; - } + if (result.length > 0) { + return { + success: true, + code: "SS001", + data: result, + message: "Successfully fetched all storage locations.", + }; + } - return returnErrorCode(STORAGE_ERROR_CODE.NO_STORAGE_LOCATIONS_FOUND); + return returnErrorCode(STORAGE_ERROR_CODE.NO_STORAGE_LOCATIONS_FOUND); }; -export const newStorage = async (name, description) => { - const [result] = await pool.query( - "INSERT INTO storage_locations (name, description) VALUES (?, ?)", - [name, description], - ); +export const newStorage = async (name: string, description: string) => { + const [result] = await pool.query( + "INSERT INTO storage_locations (name, description) VALUES (?, ?)", + [name, description], + ); - if (result.affectedRows > 0) { - return { code: "ss002" }; - } + if (result.affectedRows > 0) { + return { + success: true, + code: "SS002", + data: null, + message: "Successfully created new storage location.", + }; + } - return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_CREATED); + return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_CREATED); }; -export const updateStorage = async (uuid, values) => { - const [result] = await pool.query( - "UPDATE storage_locations SET name = ?, description = ? WHERE uuid = UUID_TO_BIN(?);", - [values.name, values.description, uuid], - ); +export const updateStorage = async ( + uuid: string, + values: { name: string; description: string }, +) => { + const [result] = await pool.query( + "UPDATE storage_locations SET name = ?, description = ? WHERE uuid = UUID_TO_BIN(?);", + [values.name, values.description, uuid], + ); - if (result.affectedRows > 0) { - return { code: "ss003" }; - } + if (result.affectedRows > 0) { + return { + success: true, + code: "SS003", + data: null, + message: "Successfully updated storage location.", + }; + } - return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_UPDATED); + return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_UPDATED); }; -export const deleteStorage = async (uuid) => { - const [result] = await pool.query( - "DELETE FROM storage_locations WHERE uuid = UUID_TO_BIN(?);", - [uuid], - ); +export const deleteStorage = async (uuid: string) => { + const [result] = await pool.query( + "DELETE FROM storage_locations WHERE uuid = UUID_TO_BIN(?);", + [uuid], + ); - if (result.affectedRows > 0) { - return { code: "ss004" }; - } + if (result.affectedRows > 0) { + return { + success: true, + code: "SS004", + data: null, + message: "Successfully deleted storage location.", + }; + } - return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_DELETED); -}; + return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_DELETED); +}; \ No newline at end of file diff --git a/backend/routes/app/products.route.ts b/backend/routes/app/products.route.ts index 4034865..ea6cd24 100644 --- a/backend/routes/app/products.route.ts +++ b/backend/routes/app/products.route.ts @@ -1,6 +1,6 @@ import express from "express"; import dotenv from "dotenv"; -import {authenticate} from "../../services/tokenService.js"; +import {authenticate} from "../../services/tokenService.ts"; import { allProducts, deleteProduct, @@ -9,182 +9,276 @@ import { setAmount, updateItem, } from "./database/products.database.ts"; -import {PRODUCT_ERROR_CODE} from "@stockhome/shared"; +import {GENERAL_ERROR_CODE, PRODUCT_ERROR_CODE} from "@stockhome/shared"; dotenv.config(); const router = express.Router(); router.post("/new-product", authenticate, async (req, res) => { - const { - name, - description, - price, - amount, - storage_location, - expiry_date, - bottling_date, - } = req.body; + const { + name, + description, + price, + amount, + storage_location, + expiry_date, + bottling_date, + } = req.body; - const result = await newProduct( - name, - description, - price, - amount, - storage_location, - expiry_date, - bottling_date, - ); + if (!name) { + return res.status(400).json({ + success: false, + code: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[0], + data: null, + message: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[1], + }); + } - if (result.code === "sp001") { - return res.status(201).json({ - success: true, - code: "sp001", - data: null, - message: "", - }); - } + const result = await newProduct( + name, + description, + price, + amount, + storage_location, + expiry_date, + bottling_date, + ); - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, + if (!result || typeof result !== "object") { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SP001") { + return res.status(201).json({ + success: true, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); router.get("/all-products", authenticate, async (req, res) => { - const result = await allProducts(); + const result = await allProducts(); - if (result.code === "sp002") { - return res.status(200).json({ - success: true, - code: "sp002", - data: result.data, - message: "", + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SP002") { + return res.status(200).json({ + success: true, + code: result.code, + data: result.data, + message: result.message, + }); + } + + if (result.code === PRODUCT_ERROR_CODE.NO_PRODUCTS_FOUND[0]) { + return res.status(404).json({ + success: false, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); - } - - if (result.code === PRODUCT_ERROR_CODE.NO_PRODUCTS_FOUND[0]) { - return res.status(404).json({ - success: false, - code: result.code, - data: null, - message: result.message, - }); - } - - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, - }); }); router.get("/view", async (req, res) => { - const uuid = req.query.uuid; + const uuid = req.query.uuid; - const result = await productDetails(uuid); + if (typeof uuid !== "string") { + return res.status(400).json({ + success: false, + code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0], + data: null, + message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1], + }); + } - if (result.code === "sp003") { - return res.status(200).json({ - success: true, - code: "sp003", - data: result.data, - message: "", + const result = await productDetails(uuid); + + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SP003") { + return res.status(200).json({ + success: true, + code: result.code, + data: result.data, + message: result.message, + }); + } + + if (result.code === PRODUCT_ERROR_CODE.PRODUCT_NOT_FOUND[0]) { + return res.status(404).json({ + success: false, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); - } - - if (result.code === PRODUCT_ERROR_CODE.PRODUCT_NOT_FOUND[0]) { - return res.status(404).json({ - success: false, - code: result.code, - data: null, - message: result.message, - }); - } - - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, - }); }); router.put("/mutate/set-amount", authenticate, async (req, res) => { - const amount = req.query.amount; - const itemUUID = req.query.item; + const amount = req.query.amount; + const itemUUID = req.query.item; - const result = await setAmount(itemUUID, amount); + if (typeof itemUUID !== "string" || typeof amount !== "number") { + return res.status(400).json({ + success: false, + code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0], + data: null, + message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1], + }); + } - if (result.code === "sp004") { - return res.status(200).json({ - success: true, - code: "sp004", - data: null, - message: "", - }); - } + const result = await setAmount(itemUUID, amount); - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SP004") { + return res.status(200).json({ + success: true, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); -router.post("/mutate/update-item", async (req, res) => { - const itemUUID = req.query.item; - const newValues = req.body; +router.post("/mutate/update-item", authenticate, async (req, res) => { + const itemUUID = req.query.item; + const newValues = req.body; - const result = await updateItem(itemUUID, newValues); + if (typeof itemUUID !== "string" || !newValues) { + return res.status(400).json({ + success: false, + code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0], + data: null, + message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1], + }); + } - if (result.code === "sp005") { - return res.status(200).json({ - success: true, - code: "sp005", - data: null, - message: "", - }); - } + const result = await updateItem(itemUUID, newValues); - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SP005") { + return res.status(200).json({ + success: true, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); router.post("/delete-selection", authenticate, async (req, res) => { - let isError = false; - const uuidArray = req.body; + const uuidArray = req.body; - for (const uuid of uuidArray) { - const response = await deleteProduct(uuid); - if (!response || response.code !== "sp006") { - isError = true; - break; + if (!Array.isArray(uuidArray) || uuidArray.length === 0) { + return res.status(400).json({ + success: false, + code: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[0], + data: null, + message: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[1], + }); + } + + for (const uuid of uuidArray) { + const response = await deleteProduct(uuid); + + if (!response) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (response.code !== "SP006") { + return res.status(500).json({ + success: false, + code: response.code || PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED[0], + data: null, + message: response.message || PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED[1], + }); + } } - } - if (isError === false) { return res.status(202).json({ - success: true, - code: "sp006", - data: null, - message: "", + success: true, + code: "SP006", + data: null, + message: "All selected products deleted successfully.", }); - } - - return res.status(500).json({ - success: false, - code: PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED[0], - data: null, - message: PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED[1], - }); }); -export default router; +export default router; \ No newline at end of file diff --git a/backend/routes/app/storage.route.ts b/backend/routes/app/storage.route.ts index 7fbe54b..90b9d29 100644 --- a/backend/routes/app/storage.route.ts +++ b/backend/routes/app/storage.route.ts @@ -2,119 +2,182 @@ import express from "express"; import dotenv from "dotenv"; import {authenticate} from "../../services/tokenService.js"; import {allStorages, deleteStorage, newStorage, updateStorage,} from "./database/storage.database.ts"; -import {STORAGE_ERROR_CODE} from "@stockhome/shared"; +import {GENERAL_ERROR_CODE, STORAGE_ERROR_CODE} from "@stockhome/shared"; dotenv.config(); const router = express.Router(); router.get("/all-storages", authenticate, async (req, res) => { - const result = await allStorages(); + const result = await allStorages(); - if (result.code === "ss001") { - return res.status(200).json({ - success: true, - code: "ss001", - data: result.data, - message: "", - }); - } + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR, + data: null, + message: "Unknown error", + }); + } - if (result.code === STORAGE_ERROR_CODE.NO_STORAGE_LOCATIONS_FOUND[0]) { - return res.status(404).json({ - success: false, - code: result.code, - data: null, - message: result.message, - }); - } + if (result.code === "SS001") { + return res.status(200).json({ + success: true, + code: result.code, + data: result.data, + message: result.message, + }); + } - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, + if (result.code === STORAGE_ERROR_CODE.NO_STORAGE_LOCATIONS_FOUND[0]) { + return res.status(404).json({ + success: false, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); router.post("/new-storage", authenticate, async (req, res) => { - const { name, description } = req.body; + const {name, description} = req.body; - let desc = description; + let desc = description; - if (!name) { - return res.status(400).json({ - success: false, - code: STORAGE_ERROR_CODE.INVALID_REQUEST_BODY[0], - data: null, - message: "invalid request body", - }); - } + if (!name) { + return res.status(400).json({ + success: false, + code: STORAGE_ERROR_CODE.INVALID_REQUEST_BODY[0], + data: null, + message: STORAGE_ERROR_CODE.INVALID_REQUEST_BODY[1], + }); + } - if (description == "") { - desc = null; - } + if (description == "") { + desc = null; + } - const result = await newStorage(name, desc); + const result = await newStorage(name, desc); - if (result.code === "ss002") { - return res.status(201).json({ - success: true, - code: "ss002", - data: null, - message: "", - }); - } + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + }); + } - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, + if (result.code === "SS002") { + return res.status(201).json({ + success: true, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); router.post("/update-storage", authenticate, async (req, res) => { - const storageUUID = req.query.storageUUID; - const values = req.body; + const storageUUID = req.query.storageUUID; + const values = req.body; - const result = await updateStorage(storageUUID, values); + if (!storageUUID || !values) { + return res.status(400).json({ + success: false, + code: STORAGE_ERROR_CODE.INVALID_PARAMETERS[0], + data: null, + message: STORAGE_ERROR_CODE.INVALID_PARAMETERS[1], + }); + } - if (result.code === "ss003") { - return res.status(201).json({ - success: true, - code: "ss003", - data: null, - message: "", - }); - } + if (typeof storageUUID !== "string") { + return res.status(400).json({ + success: false, + code: STORAGE_ERROR_CODE.INVALID_PARAMETERS[0], + data: null, + message: STORAGE_ERROR_CODE.INVALID_PARAMETERS[1], + }); + } - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, + const result = await updateStorage(storageUUID, values); + + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SS003") { + return res.status(201).json({ + success: true, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); router.delete("/delete", authenticate, async (req, res) => { - const uuid = req.query.uuid; + const uuid = req.query.uuid; - const result = await deleteStorage(uuid); + if (typeof uuid !== "string") { + return res.status(400).json({ + success: false, + code: STORAGE_ERROR_CODE.INVALID_PARAMETERS[0], + data: null, + message: STORAGE_ERROR_CODE.INVALID_PARAMETERS[1], + }); + } - if (result.code === "ss004") { - return res.status(201).json({ - success: true, - code: "ss004", - data: null, - message: "", - }); - } + const result = await deleteStorage(uuid); - return res.status(500).json({ - success: false, - code: result.code, - data: null, - message: result.message, + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SS004") { + return res.status(201).json({ + success: true, + code: result.code, + data: null, + message: result.message, + }); + } + + return res.status(500).json({ + success: false, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); diff --git a/backend/routes/app/users.route.ts b/backend/routes/app/users.route.ts index 274c0a8..421d6ae 100644 --- a/backend/routes/app/users.route.ts +++ b/backend/routes/app/users.route.ts @@ -2,6 +2,7 @@ import express from "express"; import dotenv from "dotenv"; import {authenticate, generateToken} from "../../services/tokenService.js"; import {changePassword, findUser, getSettings, loginUser, updateSettings,} from "./database/users.database.ts"; +import {GENERAL_ERROR_CODE, USER_ERROR_CODE} from "@stockhome/shared"; dotenv.config(); const router = express.Router(); @@ -11,42 +12,71 @@ router.post("/verify-token", authenticate, async (req, res) => { }); router.post("/update-app-settings", authenticate, async (req, res) => { - const result = await updateSettings(req.body); + const values = req.body; - if (result.code === "su003") { + if (!values || Object.keys(values).length === 0) { + return res.status(400).json({ + success: false, + code: USER_ERROR_CODE.INVALID_REQUEST_BODY[0], + data: null, + message: USER_ERROR_CODE.INVALID_REQUEST_BODY[1], + }); + } + + const result = await updateSettings(values); + + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SU003") { return res.status(201).json({ success: true, - code: "su003", + code: result.code, data: result.data, - message: null, + message: result.message, }); } return res.status(500).json({ success: false, - code: result.code, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], data: null, - message: result.message, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); router.get("/settings", authenticate, async (req, res) => { const result = await getSettings(); - if (result.code === "su004") { + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SU004") { return res.status(200).json({ success: true, - code: "su004", + code: result.code, data: result.data, - message: null, + message: result.message, }); } return res.status(500).json({ success: false, - code: result.code, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], data: null, - message: result.message, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); @@ -54,9 +84,27 @@ router.post("/login", async (req, res) => { const username = req.body.username; const password = req.body.password; + if (!username || !password) { + return res.status(400).json({ + success: false, + code: USER_ERROR_CODE.INVALID_REQUEST_BODY[0], + data: null, + message: USER_ERROR_CODE.INVALID_REQUEST_BODY[1], + }); + } + const result = await findUser(username, password); - if (result.code === "EU001") { + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === USER_ERROR_CODE.WRONG_USERNAME_PASSWORD[0]) { return res.status(404).json({ success: false, code: result.code, @@ -65,7 +113,7 @@ router.post("/login", async (req, res) => { }); } - if (result.code === "EU002") { + if (result.code === USER_ERROR_CODE.USER_IS_DEACTIVATED[0]) { return res.status(403).json({ success: false, code: result.code, @@ -74,22 +122,59 @@ router.post("/login", async (req, res) => { }); } - if (result.code === "su001") { - const token = await generateToken(result.data); - const login = await loginUser(result.data.username); - - if (login.code !== "su002") { + if (result.code === "SU001") { + if (!result.data) { return res.status(500).json({ success: false, - code: login.code, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], data: null, - message: login.message, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + const token = await generateToken(result.data); + + if (!token) { + return res.status(500).json({ + success: false, + code: USER_ERROR_CODE.TOKEN_GENERATION_FAILED[0], + data: null, + message: USER_ERROR_CODE.TOKEN_GENERATION_FAILED[1], + }); + } + + if (!result.data) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + const login = await loginUser(result.data.username); + + if (!login) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (login.code !== "SU002") { + return res.status(500).json({ + success: false, + code: login.code || USER_ERROR_CODE.LOGIN_FAILED[0], + data: null, + message: login.message || USER_ERROR_CODE.LOGIN_FAILED[1], }); } return res.status(202).json({ success: true, - code: "su001", + code: "SU001", data: { token, }, @@ -99,31 +184,62 @@ router.post("/login", async (req, res) => { return res.status(500).json({ success: false, - code: result.code, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], data: null, - message: result.message, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); router.post("/change-password", authenticate, async (req, res) => { const currentPassword = req.body.currentPassword; const newPassword = req.body.newPassword; + + if (!req.user) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + const username = req.user.username; + if (!currentPassword || !newPassword) { + return res.status(400).json({ + success: false, + code: USER_ERROR_CODE.INVALID_REQUEST_BODY[0], + data: null, + message: USER_ERROR_CODE.INVALID_REQUEST_BODY[1], + }); + } + const result = await changePassword(username, currentPassword, newPassword); - if (result.code === "su005") { + if (!result) { + return res.status(500).json({ + success: false, + code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], + }); + } + + if (result.code === "SU005") { return res.status(202).json({ success: true, code: result.code, + data: null, + message: result.message, }); } return res.status(500).json({ success: false, - code: result.code, - message: result.message, + code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0], + data: null, + message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1], }); }); -export default router; +export default router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js deleted file mode 100644 index d02b8c3..0000000 --- a/backend/server.js +++ /dev/null @@ -1,138 +0,0 @@ -import express from "express"; -import cors from "cors"; -import dotenv from "dotenv"; -import mysql from "mysql2"; -import { readFile } from "fs/promises"; -dotenv.config(); -const app = express(); -app.set("view engine", "ejs"); - -const port = 8004; - -app.use(cors()); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); - -const pool = mysql - .createPool({ - host: process.env.DB_HOST, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - }) - .promise(); - -// frontend routes -import userRouter from "./routes/app/users.route.js"; -app.use("/users", userRouter); - -import productRouter from "./routes/app/products.route.js"; -app.use("/products", productRouter); - -import storageRouter from "./routes/app/storage.route.js"; -app.use("/storage", storageRouter); - -app.listen(port, () => { - runStartup(port); -}); - -// Startup code -const runStartup = async (port) => { - // Check if database is configured; create schema if app_settings is missing. - let firstStartupValue = null; - try { - const [firstResponse] = await pool.query( - `SELECT value FROM app_settings WHERE name = "first-startup";`, - ); - firstStartupValue = firstResponse[0]?.value ?? null; - } catch (err) { - if (err?.code !== "ER_NO_SUCH_TABLE") { - throw err; - } - } - - if (firstStartupValue !== "false") { - const schemaPath = new URL("./database.scheme.sql", import.meta.url); - const schemaSql = await readFile(schemaPath, "utf8"); - const statements = schemaSql - .split(";") - .map((statement) => statement.trim()) - .filter((statement) => statement.length > 0); - - for (const statement of statements) { - await pool.query(statement); - } - - // create admin credentials - const [result] = await pool.query( - `SELECT value FROM app_settings WHERE name = "first-startup";`, - ); - - if (result[0]?.value === "true") { - const insertResult = await insertFirstData(); - - if (insertResult.affectedRows > 0) { - // print out admin credentials - console.log("Successfully created admin user!"); - console.log("Username: admin"); - console.log("Password: admin"); - - // Set startup variable to true if scheme insert was successfull - const [scndResponse] = await pool.query( - `UPDATE app_settings SET value = "false" WHERE name = "first-startup";`, - ); - - if (scndResponse.affectedRows > 0) { - console.log("Database settet up successfully!"); - } else { - console.error("There was an error while setting up the database!"); - } - } else { - console.error("Error while creating admin user."); - } - } - } - - console.log("Everything is settet up successfully!"); - console.log(`Backend is running on http://localhost:${port}`); -}; - -const insertFirstData = async () => { - const [insertResult] = await pool.query( - `INSERT INTO users (username, first_name, last_name, email, password, is_admin) VALUES ("admin", "admin", "admin", "admin@example.com", "admin", 1)`, - ); - - await pool.query( - `INSERT INTO storage_locations (name, description) VALUES (?, ?);`, - ["Default Storage", "Initial storage location"], - ); - - const [storageRows] = await pool.query( - `SELECT uuid FROM storage_locations WHERE name = ? LIMIT 1;`, - ["Default Storage"], - ); - - const storageUuid = storageRows[0]?.uuid ?? null; - if (storageUuid) { - await pool.query( - `INSERT INTO products (name, description, price, amount, storage_location, picture) VALUES (?, ?, ?, ?, ?, ?);`, - [ - "Welcome Product", - "Your first item in Stockhome", - "0.00", - 1, - storageUuid, - null, - ], - ); - } - - console.log("Welcome product is ready...") - return insertResult; -}; - -// error handling code -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).send("Something broke!"); -}); diff --git a/backend/server.ts b/backend/server.ts new file mode 100644 index 0000000..48b396a --- /dev/null +++ b/backend/server.ts @@ -0,0 +1,147 @@ +import type {NextFunction, Request, Response} from "express"; +import express from "express"; +import cors from "cors"; +import dotenv from "dotenv"; +import mysql, {type ResultSetHeader, type RowDataPacket} from "mysql2"; +import {readFile} from "fs/promises"; +// frontend routes +import userRouter from "./routes/app/users.route.ts"; +import productRouter from "./routes/app/products.route.ts"; +import storageRouter from "./routes/app/storage.route.ts"; + +dotenv.config(); +const app = express(); +app.set("view engine", "ejs"); + +const port = 8004; + +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({extended: true})); + +const pool = mysql + .createPool({ + host: process.env.DB_HOST!, + user: process.env.DB_USER!, + password: process.env.DB_PASSWORD!, + database: process.env.DB_NAME!, + }) + .promise(); + +app.use("/users", userRouter); + +app.use("/products", productRouter); + +app.use("/storage", storageRouter); + +app.listen(port, () => { + runStartup(port); +}); + +// Startup code +const runStartup = async (port: number) => { + let firstStartupValue: string | null = null; + try { + const [firstResponse] = await pool.query( + `SELECT value + FROM app_settings + WHERE name = "first-startup";`, + ); + firstStartupValue = firstResponse[0]?.value ?? null; + } catch (err: unknown) { + if (err instanceof Error && (err as NodeJS.ErrnoException & { code?: string }).code !== "ER_NO_SUCH_TABLE") { + throw err; + } + } + + if (firstStartupValue !== "false") { + const schemaPath = new URL("./database.scheme.sql", import.meta.url); + const schemaSql = await readFile(schemaPath, "utf8"); + const statements = schemaSql + .split(";") + .map((statement: string) => statement.trim()) + .filter((statement: string) => statement.length > 0); + + for (const statement of statements) { + await pool.query(statement); + } + + const [result] = await pool.query( + `SELECT value + FROM app_settings + WHERE name = "first-startup";`, + ); + + if (result[0]?.value === "true") { + const insertResult = await insertFirstData(); + + if (insertResult.affectedRows > 0) { + console.log("Successfully created admin user!"); + console.log("Username: admin"); + console.log("Password: admin"); + + const [scndResponse] = await pool.query( + `UPDATE app_settings + SET value = "false" + WHERE name = "first-startup";`, + ); + + if (scndResponse.affectedRows > 0) { + console.log("Database settet up successfully!"); + } else { + console.error("There was an error while setting up the database!"); + } + } else { + console.error("Error while creating admin user."); + } + } + } + + console.log("Everything is settet up successfully!"); + console.log(`Backend is running on http://localhost:${port}`); +}; + +const insertFirstData = async (): Promise => { + const [insertResult] = await pool.query( + `INSERT INTO users (username, first_name, last_name, email, password, is_admin) + VALUES ("admin", "admin", "admin", "admin@example.com", "admin", 1)`, + ); + + await pool.query( + `INSERT INTO storage_locations (name, description) + VALUES (?, ?);`, + ["Default Storage", "Initial storage location"], + ); + + const [storageRows] = await pool.query( + `SELECT uuid + FROM storage_locations + WHERE name = ? LIMIT 1;`, + ["Default Storage"], + ); + + const storageUuid: string | null = storageRows[0]?.uuid ?? null; + if (storageUuid) { + await pool.query( + `INSERT INTO products (name, description, price, amount, storage_location, picture) + VALUES (?, ?, ?, ?, ?, ?);`, + [ + "Welcome Product", + "Your first item in Stockhome", + "0.00", + 1, + storageUuid, + null, + ], + ); + } + + console.log("Welcome product is ready..."); + return insertResult; +}; + +// error handling code +app.use((err: Error, req: Request, res: Response, next: NextFunction) => { + console.error(err.stack); + res.status(500).send("Something broke!"); +}); \ No newline at end of file diff --git a/backend/services/helperFuncs.ts b/backend/services/helperFuncs.ts index 7ae1f96..5ecdb57 100644 --- a/backend/services/helperFuncs.ts +++ b/backend/services/helperFuncs.ts @@ -1,9 +1,14 @@ -export const returnErrorCode = (errorKey) => { +import type {ErrorCode} from "../misc/types.ts"; + +export const returnErrorCode = (errorKey: ErrorCode) => { if (!Array.isArray(errorKey) || errorKey.length < 2) { return false; } - return {code: errorKey[0], message: errorKey[1], data: null}; -}; - -export default returnErrorCode; \ No newline at end of file + return { + success: false, + code: errorKey[0], + data: null, + message: errorKey[1] + } +}; \ No newline at end of file diff --git a/backend/services/tokenService.ts b/backend/services/tokenService.ts index af2127b..004f2e7 100644 --- a/backend/services/tokenService.ts +++ b/backend/services/tokenService.ts @@ -1,25 +1,36 @@ -import {jwtVerify, SignJWT} from "jose"; +import type {NextFunction, Request, Response} from "express"; +import {type JWTPayload, jwtVerify, SignJWT} from "jose"; import env from "dotenv"; env.config(); const secret = new TextEncoder().encode(process.env.SECRET_KEY); -export async function generateToken(payload) { - return await new SignJWT(payload) - .setProtectedHeader({alg: "HS256"}) - .setIssuedAt() - .setExpirationTime("24h") // Token valid for 24 hours - .sign(secret); +export type AuthTokenPayload = JWTPayload & { + username: string; +}; + +declare module "express-serve-static-core" { + interface Request { + user?: AuthTokenPayload; + } } -export async function authenticate(req, res, next) { - const authHeader = req.headers["authorization"]; - const token = authHeader && authHeader.split(" ")[1]; // Bearer - - if (token == null) return res.sendStatus(401); // No token present - - const { payload } = await jwtVerify(token, secret); - req.user = payload; - - next(); +export async function generateToken(payload: AuthTokenPayload) { + return await new SignJWT(payload) + .setProtectedHeader({alg: "HS256"}) + .setIssuedAt() + .setExpirationTime("24h") + .sign(secret); } + +export async function authenticate(req: Request, res: Response, next: NextFunction) { + const authHeader = req.headers["authorization"]; + const token = authHeader && authHeader.split(" ")[1]; + + if (token == null) return res.sendStatus(401); + + const {payload} = await jwtVerify(token, secret); + req.user = payload; + + next(); +} \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..21ed323 --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,45 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + "outDir": "./dist", + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "esnext", + "target": "esnext", + "esModuleInterop": true, + "moduleResolution": "bundler", + // For nodejs: + // "lib": ["esnext"], + "types": [ + "node" + ], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "allowImportingTsExtensions": true, + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true + } +} diff --git a/backend/vite.config.ts b/backend/vite.config.ts index e69de29..7fe589d 100644 --- a/backend/vite.config.ts +++ b/backend/vite.config.ts @@ -0,0 +1,32 @@ +import {defineConfig} from 'vite' +import path from 'path' +import {dirname} from "node:path"; +import {fileURLToPath} from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export default defineConfig({ + root: 'src', + build: { + outDir: '../dist', + emptyOutDir: true, + sourcemap: true, + minify: 'esbuild', // Use esbuild for minification (default) + assetsDir: 'assets', + rollupOptions: { + input: path.resolve(__dirname, 'src/index.html'), + output: { + assetFileNames: 'assets/[name]-[hash][extname]' // Hashing for cache busting + } + } + }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), // Optional alias for cleaner imports + } + }, + define: { + 'process.env.NODE_ENV': '"production"' // Inject environment variables + } +}) \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index e69de29..e3254ff 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -0,0 +1,5 @@ +import js from "@eslint/js"; + +export default [ + js.configs.recommended, +]; \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index c3c21da..f733e3a 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,12 +4,15 @@ This template provides a minimal setup to get React working in Vite with HMR and Currently, two official plugins are available: -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) + uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) + uses [SWC](https://swc.rs/) ## React Compiler -The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information. +The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for +more information. Note: This will impact Vite dev & build performances. @@ -45,31 +48,34 @@ export default defineConfig([ ]) ``` -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: +You can also +install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) +and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) +for React-specific lint rules: ```js -// eslint.config.js +// eslint.config.mjs import reactX from 'eslint-plugin-react-x' import reactDom from 'eslint-plugin-react-dom' export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, }, - }, ]) ``` diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 1ffef60..ae3a9b4 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -3,5 +3,11 @@ "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } - ] + ], + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "esModuleInterop": true, + "moduleResolution": "node" + } } diff --git a/package-lock.json b/package-lock.json index 6b9d112..cc15712 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,35 +1,44 @@ { "name": "stockhome", - "version": "1.0.0", + "version": "0.1.0-dev", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "stockhome", - "version": "1.0.0", + "version": "0.1.0-dev", "license": "ISC", "workspaces": [ "backend", - "frontend" + "frontend", + "shared" ], "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", "concurrently": "^10.0.3" } }, "backend": { - "version": "1.0.0", + "version": "0.0.0-dev", "license": "ISC", "dependencies": { + "@stockhome/shared": "1.0.0", "cors": "^2.8.5", "dotenv": "^17.2.3", "ejs": "^3.1.10", "express": "^5.2.1", "jose": "^6.0.12", "mysql2": "^3.16.0" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^26.1.0" } }, "frontend": { - "version": "0.0.0", + "version": "0.0.0-dev", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", @@ -1598,6 +1607,33 @@ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@stockhome/shared": { + "resolved": "shared", + "link": true + }, "node_modules/@tailwindcss/node": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", @@ -2253,6 +2289,37 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -2267,6 +2334,38 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/js-cookie": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", @@ -2318,6 +2417,20 @@ "@types/node": "*" } }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", @@ -2347,6 +2460,44 @@ "@types/react": "*" } }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.63.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", @@ -2616,6 +2767,35 @@ } } }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -2770,6 +2950,96 @@ "node": "18 || 20 || >=22" } }, + "node_modules/barrelsby": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/barrelsby/-/barrelsby-2.8.1.tgz", + "integrity": "sha512-barN2MVKqUVwmjRy3JLSMYufrBDcdWUc2pjlR0V9P8S3aMvvJ4StFz1GJMzEi5GBoQlnBIWOcCxBDzI2xfaaGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs": "^17.0.10", + "signale": "^1.4.0", + "yargs": "^17.4.1" + }, + "bin": { + "barrelsby": "bin/cli.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/barrelsby/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/barrelsby/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/barrelsby/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/barrelsby/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/barrelsby/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.10.42", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", @@ -3011,6 +3281,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concurrently": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", @@ -3174,6 +3451,13 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -3701,6 +3985,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3763,6 +4054,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3801,6 +4099,29 @@ } } }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4082,6 +4403,16 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -4409,6 +4740,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -4452,6 +4790,13 @@ "json-buffer": "3.0.1" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4733,6 +5078,54 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4881,6 +5274,38 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5105,6 +5530,13 @@ "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5174,6 +5606,115 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -5310,6 +5851,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", @@ -5748,6 +6306,89 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/signale/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5816,6 +6457,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -6003,6 +6654,13 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", @@ -6074,6 +6732,76 @@ } } }, + "node_modules/unplugin-dts": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.0.3.tgz", + "integrity": "sha512-/GR887wfG4r1cWyt1UZsLRuMIjsmEbGkS9yJrz+0dsToHAYUD5CTyP3JMGVLv25j9K0mJcwAVvZno/aTuSUvNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "@volar/typescript": "^2.4.26", + "compare-versions": "^6.1.1", + "debug": "^4.4.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "unplugin": "^2.3.2" + }, + "peerDependencies": { + "@microsoft/api-extractor": ">=7", + "@rspack/core": "^1", + "@vue/language-core": "^3.1.5", + "esbuild": "*", + "rolldown": "*", + "rollup": ">=3", + "typescript": ">=4", + "vite": ">=3", + "webpack": "^4 || ^5" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "@vue/language-core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-dts/node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -6219,6 +6947,32 @@ } } }, + "node_modules/vite-plugin-dts": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-5.0.3.tgz", + "integrity": "sha512-gIth6NdCEHWPiiRMCK3N6C8WjvdsrtEQrmsiG8h6Ov+lFP+b07Y+wcs9H0H7n146l0PDTYK4cQN1vgeG1pMdRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "unplugin-dts": "1.0.3" + }, + "peerDependencies": { + "@microsoft/api-extractor": ">=7", + "rollup": ">=3", + "vite": ">=3" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -6228,6 +6982,13 @@ "node": ">=0.10.0" } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", @@ -6421,6 +7182,14 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "shared": { + "name": "@stockhome/shared", + "version": "1.0.0", + "devDependencies": { + "barrelsby": "^2.8.1", + "vite-plugin-dts": "^5.0.3" + } } } } diff --git a/package.json b/package.json index a834cb1..966ab29 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "private": true, "workspaces": [ "backend", - "frontend" + "frontend", + "shared" ], "version": "0.1.0-dev", "description": "![React](https://img.shields.io/badge/React-61DAFB?logo=react&logoColor=000&style=flat) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=fff&style=flat) ![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=fff&style=flat) ![Tailwind%20CSS](https://img.shields.io/badge/Tailwind%20CSS-38B2AC?logo=tailwindcss&logoColor=fff&style=flat) ![MUI](https://img.shields.io/badge/MUI-007FFF?logo=mui&logoColor=fff&style=flat) ![TanStack%20Query](https://img.shields.io/badge/TanStack%20Query-FF4154?logo=reactquery&logoColor=fff&style=flat) ![React%20Router](https://img.shields.io/badge/React%20Router-CA4245?logo=reactrouter&logoColor=fff&style=flat) [![TanStack%20Form](https://img.shields.io/badge/TanStack%20Form-EC5990?logo=reacthookform&logoColor=fff)](#) ![Node.js](https://img.shields.io/badge/Node.js-339933?logo=nodedotjs&logoColor=fff&style=flat) ![Express](https://img.shields.io/badge/Express-000000?logo=express&logoColor=fff&style=flat) ![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=fff&style=flat) ![Docker](https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=fff&style=flat) ![Nginx](https://img.shields.io/badge/Nginx-009639?logo=nginx&logoColor=fff&style=flat)", @@ -13,7 +14,8 @@ "dev:backend": "npm run dev --workspace=backend", "dev:docker": "docker compose -f docker-compose.dev.yml up -d --wait", "dev": "npm run dev:docker && concurrently --kill-others \"npm:dev:frontend\" \"npm:dev:backend\"", - "dev:stop": "docker compose -f docker-compose.dev.yml down" + "dev:stop": "docker compose -f docker-compose.dev.yml down", + "buildProd": "" }, "repository": { "type": "git", @@ -24,6 +26,9 @@ "license": "ISC", "type": "commonjs", "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", "concurrently": "^10.0.3" } } diff --git a/shared/package.json b/shared/package.json index e69de29..014804d 100644 --- a/shared/package.json +++ b/shared/package.json @@ -0,0 +1,17 @@ +{ + "name": "@stockhome/shared", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "prebuild": "barrelsby --directory src --delete --single", + "build": "tsc", + "lint": "eslint ." + }, + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "devDependencies": { + "barrelsby": "^2.8.1", + "vite-plugin-dts": "^5.0.3" + } +} diff --git a/shared/src/error/errorcodes.enum.ts b/shared/src/error/errorcodes.enum.ts index dcb6f89..73e5d75 100644 --- a/shared/src/error/errorcodes.enum.ts +++ b/shared/src/error/errorcodes.enum.ts @@ -1,4 +1,35 @@ -export enum ERROR_CODE { - WRONG_USER_NAME_PASSWORD = 'EU001', - UNEXPECTED_FORMAT = 'ES001', -} \ No newline at end of file +export const GENERAL_ERROR_CODE = { + UNEXPECTED_SERVER_ERROR: ["EG001", "Unexpected server error"], +} as const; + +export const USER_ERROR_CODE = { + WRONG_USERNAME_PASSWORD: ["EU001", "Username or password is wrong"], + USER_IS_DEACTIVATED: ["EU002", "User is deactivated"], + INVALID_REQUEST_BODY: ["EU003", "Invalid request body. Username and password are required."], + INVALID_PARAMETERS: ["EU004", "Invalid or missing parameters."], + TOKEN_GENERATION_FAILED: ["EU005", "Failed to generate user token."], + LOGIN_FAILED: ["EU006", "Failed to log in user."], + PASSWORD_CHANGE_FAILED: ["EU007", "Failed to change password."], + SETTINGS_NOT_UPDATED: ["EU008", "Settings could not be updated."], + SETTINGS_NOT_FOUND: ["EU009", "Settings could not be retrieved."], +} as const; + +export const STORAGE_ERROR_CODE = { + INVALID_REQUEST_BODY: ["ES001", "Invalid request body"], + INVALID_PARAMETERS: ["ES002", "Parameter storageUUID or/and parameter values are/is missing."], + STORAGE_NOT_UPDATED: ["ES003", "Storage is not updated."], + STORAGE_NOT_DELETED: ["ES004", "Storage is not deleted."], + NO_STORAGE_LOCATIONS_FOUND: ["ES005", "No storage locations found"], + STORAGE_NOT_CREATED: ["ES006", "Storage is not created."], +} as const; + +export const PRODUCT_ERROR_CODE = { + PRODUCT_NOT_CREATED: ["EP001", "Error while creating product"], + NO_PRODUCTS_FOUND: ["EP002", "No products found"], + PRODUCT_NOT_FOUND: ["EP003", "Product not found"], + PRODUCT_AMOUNT_NOT_UPDATED: ["EP004", "Error while updating product amount"], + PRODUCT_NOT_UPDATED: ["EP005", "Error while updating product"], + PRODUCT_NOT_DELETED: ["EP006", "Error while deleting product"], + INVALID_REQUEST_BODY: ["EP007", "Invalid request body. Name is required."], + INVALID_PARAMETERS: ["EP008", "Invalid or missing parameters."], +} as const; \ No newline at end of file diff --git a/shared/tsconfig.json b/shared/tsconfig.json index 85a84bc..1c74c79 100644 --- a/shared/tsconfig.json +++ b/shared/tsconfig.json @@ -9,6 +9,8 @@ // See also https://aka.ms/tsconfig/module "module": "esnext", "target": "esnext", + "esModuleInterop": true, + "moduleResolution": "node", "types": [], // For nodejs: // "lib": ["esnext"], diff --git a/shared/vite.config.ts b/shared/vite.config.ts index b87fac5..ca5a445 100644 --- a/shared/vite.config.ts +++ b/shared/vite.config.ts @@ -1,15 +1,9 @@ import { defineConfig } from "vite"; -import react, { reactCompilerPreset } from "@vitejs/plugin-react"; -import babel from "@rolldown/plugin-babel"; -import tailwindcss from "@tailwindcss/vite"; -import { TanStackRouterVite } from "@tanstack/router-vite-plugin"; +import dts from "vite-plugin-dts"; // https://vite.dev/config/ export default defineConfig({ plugins: [ - react(), - tailwindcss(), - TanStackRouterVite(), - babel({ presets: [reactCompilerPreset()] }), + dts(), ], });