refactor: change backend to typescript and update types and variables accordingly

This commit is contained in:
2026-07-08 10:54:07 +02:00
parent 36ec8da1a0
commit 84fa2e0ab0
24 changed files with 1911 additions and 615 deletions
+11
View File
@@ -0,0 +1,11 @@
import type {AuthTokenPayload} from "../services/tokenService.js";
declare global {
namespace Express {
interface Request {
user?: AuthTokenPayload;
}
}
}
export {};
+8
View File
@@ -0,0 +1,8 @@
export type ErrorCode = readonly [code: string, message: string];
export interface Response {
success: boolean;
code: string;
data: any;
message: string;
}
+9 -4
View File
@@ -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"
}
}
}
+158 -120
View File
@@ -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<boolean | { success: boolean; code: string; data: null; message: string }> => {
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<ResultSetHeader>(
"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<RowDataPacket[]>(
`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<RowDataPacket[]>(`
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<ResultSetHeader>(
`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<ResultSetHeader>(
`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<ResultSetHeader>(
`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);
};
+66 -44
View File
@@ -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<RowDataPacket[]>(
"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<ResultSetHeader>(
"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<ResultSetHeader>(
"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<ResultSetHeader>(
"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);
};
+231 -137
View File
@@ -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;
+144 -81
View File
@@ -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],
});
});
+143 -27
View File
@@ -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;
-138
View File
@@ -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!");
});
+147
View File
@@ -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<RowDataPacket[]>(
`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<RowDataPacket[]>(
`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<ResultSetHeader>(
`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<ResultSetHeader> => {
const [insertResult] = await pool.query<ResultSetHeader>(
`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<RowDataPacket[]>(
`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!");
});
+10 -5
View File
@@ -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;
return {
success: false,
code: errorKey[0],
data: null,
message: errorKey[1]
}
};
+28 -17
View File
@@ -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 <token>
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<AuthTokenPayload>(token, secret);
req.user = payload;
next();
}
+45
View File
@@ -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
}
}
+32
View File
@@ -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
}
})