refactor: edit tailwindcss classes and change code formatting, therefore added .prettierrc file
fix: frontend bug where the passwords won't be checked when the password is changed
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
+12
-1
@@ -2,34 +2,45 @@ FROM node:22-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Workspace manifests (npm workspaces need these present)
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
COPY backend/package.json backend/
|
COPY backend/package.json backend/
|
||||||
COPY frontend/package.json frontend/
|
COPY frontend/package.json frontend/
|
||||||
COPY shared/package.json shared/
|
COPY shared/package.json shared/
|
||||||
|
|
||||||
|
# Install all deps for the monorepo (includes devDeps for building)
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy sources required to build shared + backend
|
||||||
COPY shared/ shared/
|
COPY shared/ shared/
|
||||||
COPY backend/ backend/
|
COPY backend/ backend/
|
||||||
|
|
||||||
|
# Build shared (now produces dist/esm + dist/cjs + dist/types) and then backend
|
||||||
RUN npm run build --workspace=shared
|
RUN npm run build --workspace=shared
|
||||||
RUN npm run build --workspace=backend
|
RUN npm run build --workspace=backend
|
||||||
|
|
||||||
|
|
||||||
FROM node:22-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Workspace manifests again (so npm can install prod deps for backend + shared)
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
COPY backend/package.json backend/
|
COPY backend/package.json backend/
|
||||||
COPY frontend/package.json frontend/
|
COPY frontend/package.json frontend/
|
||||||
COPY shared/package.json shared/
|
COPY shared/package.json shared/
|
||||||
|
|
||||||
|
# Install prod deps only
|
||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# Copy runtime artifacts
|
||||||
COPY --from=builder /app/backend/dist backend/dist
|
COPY --from=builder /app/backend/dist backend/dist
|
||||||
COPY --from=builder /app/backend/database.scheme.sql backend/
|
COPY --from=builder /app/backend/database.scheme.sql backend/database.scheme.sql
|
||||||
|
|
||||||
|
# Copy the full shared dist output (esm/cjs/types)
|
||||||
COPY --from=builder /app/shared/dist shared/dist
|
COPY --from=builder /app/shared/dist shared/dist
|
||||||
|
COPY shared/package.json shared/package.json
|
||||||
|
|
||||||
EXPOSE 8004
|
EXPOSE 8004
|
||||||
WORKDIR /app/backend
|
WORKDIR /app/backend
|
||||||
|
|||||||
@@ -1,48 +1,61 @@
|
|||||||
import mysql, {type ResultSetHeader, type RowDataPacket} from "mysql2/promise";
|
import mysql, {
|
||||||
|
type ResultSetHeader,
|
||||||
|
type RowDataPacket,
|
||||||
|
} from "mysql2/promise";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import {returnErrorCode} from "../../../services/helperFuncs.js";
|
import { returnErrorCode } from "../../../services/helperFuncs.js";
|
||||||
import {PRODUCT_ERROR_CODE} from "@stockhome/shared";
|
import { PRODUCT_ERROR_CODE } from "@stockhome/shared";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const pool = mysql.createPool({
|
const pool = mysql.createPool({
|
||||||
host: process.env.DB_HOST!,
|
host: process.env.DB_HOST!,
|
||||||
user: process.env.DB_USER!,
|
user: process.env.DB_USER!,
|
||||||
password: process.env.DB_PASSWORD!,
|
password: process.env.DB_PASSWORD!,
|
||||||
database: process.env.DB_NAME!,
|
database: process.env.DB_NAME!,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const newProduct = async (
|
export const newProduct = async (
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
price: string,
|
price: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
storage_location: string,
|
storage_location: string,
|
||||||
expiry_date: string,
|
expiry_date: string,
|
||||||
bottling_date: string,
|
bottling_date: string,
|
||||||
): Promise<boolean | { success: boolean; code: string; data: null; message: string }> => {
|
): Promise<
|
||||||
const newPrice = price !== "" ? price : null;
|
boolean | { success: boolean; code: string; data: null; message: string }
|
||||||
|
> => {
|
||||||
|
const newPrice = price !== "" ? price : null;
|
||||||
|
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
"INSERT INTO products (name, description, price, amount, storage_location, expiry_date, bottling_date) VALUES (?, ?, ?, ?, UUID_TO_BIN(?), ?, ?)",
|
"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],
|
[
|
||||||
);
|
name,
|
||||||
|
description,
|
||||||
|
newPrice,
|
||||||
|
amount,
|
||||||
|
storage_location,
|
||||||
|
expiry_date,
|
||||||
|
bottling_date,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SP001",
|
code: "SP001",
|
||||||
data: null,
|
data: null,
|
||||||
message: "New Product successfully created.",
|
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: string) => {
|
export const productDetails = async (uuid: string) => {
|
||||||
const [result] = await pool.query<RowDataPacket[]>(
|
const [result] = await pool.query<RowDataPacket[]>(
|
||||||
`SELECT BIN_TO_UUID(p.uuid) AS uuid,
|
`SELECT BIN_TO_UUID(p.uuid) AS uuid,
|
||||||
p.name,
|
p.name,
|
||||||
p.description,
|
p.description,
|
||||||
p.price,
|
p.price,
|
||||||
@@ -55,23 +68,23 @@ export const productDetails = async (uuid: string) => {
|
|||||||
FROM products p
|
FROM products p
|
||||||
JOIN storage_locations s ON p.storage_location = s.uuid
|
JOIN storage_locations s ON p.storage_location = s.uuid
|
||||||
WHERE p.uuid = UUID_TO_BIN(?)`,
|
WHERE p.uuid = UUID_TO_BIN(?)`,
|
||||||
[uuid],
|
[uuid],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SP003",
|
code: "SP003",
|
||||||
data: result[0],
|
data: result[0],
|
||||||
message: "Product details successfully loaded.",
|
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 () => {
|
export const allProducts = async () => {
|
||||||
const [result] = await pool.query<RowDataPacket[]>(`
|
const [result] = await pool.query<RowDataPacket[]>(`
|
||||||
SELECT BIN_TO_UUID(p.uuid) AS uuid,
|
SELECT BIN_TO_UUID(p.uuid) AS uuid,
|
||||||
p.name,
|
p.name,
|
||||||
p.description,
|
p.description,
|
||||||
@@ -89,52 +102,52 @@ export const allProducts = async () => {
|
|||||||
WHERE p.deleted = 0
|
WHERE p.deleted = 0
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SP002",
|
code: "SP002",
|
||||||
data: result,
|
data: result,
|
||||||
message: "All products successfully loaded.",
|
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: string, amount: number) => {
|
export const setAmount = async (itemUUID: string, amount: number) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
`UPDATE products
|
`UPDATE products
|
||||||
SET amount = ?
|
SET amount = ?
|
||||||
WHERE uuid = UUID_TO_BIN(?)`,
|
WHERE uuid = UUID_TO_BIN(?)`,
|
||||||
[amount, itemUUID],
|
[amount, itemUUID],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SP004",
|
code: "SP004",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Amount successfully updated.",
|
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 (
|
export const updateItem = async (
|
||||||
itemUUID: string,
|
itemUUID: string,
|
||||||
newValues: {
|
newValues: {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
price: string;
|
price: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
storage_location_uuid: string;
|
storage_location_uuid: string;
|
||||||
expiry_date: string;
|
expiry_date: string;
|
||||||
bottling_date: string;
|
bottling_date: string;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
`UPDATE products
|
`UPDATE products
|
||||||
SET name = ?,
|
SET name = ?,
|
||||||
description = ?,
|
description = ?,
|
||||||
price = ?,
|
price = ?,
|
||||||
@@ -143,47 +156,46 @@ export const updateItem = async (
|
|||||||
expiry_date = ?,
|
expiry_date = ?,
|
||||||
bottling_date = ?
|
bottling_date = ?
|
||||||
WHERE uuid = UUID_TO_BIN(?);`,
|
WHERE uuid = UUID_TO_BIN(?);`,
|
||||||
[
|
[
|
||||||
newValues.name,
|
newValues.name,
|
||||||
newValues.description,
|
newValues.description,
|
||||||
newValues.price,
|
newValues.price,
|
||||||
newValues.amount,
|
newValues.amount,
|
||||||
newValues.storage_location_uuid,
|
newValues.storage_location_uuid,
|
||||||
newValues.expiry_date,
|
newValues.expiry_date,
|
||||||
newValues.bottling_date,
|
newValues.bottling_date,
|
||||||
itemUUID,
|
itemUUID,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SP005",
|
code: "SP005",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Item updated successfully.",
|
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: string) => {
|
export const deleteProduct = async (uuid: string) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
`UPDATE products
|
`UPDATE products
|
||||||
SET deleted = 1
|
SET deleted = 1
|
||||||
WHERE uuid = UUID_TO_BIN(?);`,
|
WHERE uuid = UUID_TO_BIN(?);`,
|
||||||
[uuid],
|
[uuid],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SP006",
|
code: "SP006",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Product deleted successfully.",
|
message: "Product deleted successfully.",
|
||||||
};
|
};
|
||||||
;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED);
|
return returnErrorCode(PRODUCT_ERROR_CODE.PRODUCT_NOT_DELETED);
|
||||||
};
|
};
|
||||||
@@ -1,87 +1,90 @@
|
|||||||
import mysql, {type ResultSetHeader, type RowDataPacket} from "mysql2/promise";
|
import mysql, {
|
||||||
|
type ResultSetHeader,
|
||||||
|
type RowDataPacket,
|
||||||
|
} from "mysql2/promise";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import {STORAGE_ERROR_CODE} from "@stockhome/shared";
|
import { STORAGE_ERROR_CODE } from "@stockhome/shared";
|
||||||
import {returnErrorCode} from "../../../services/helperFuncs.js";
|
import { returnErrorCode } from "../../../services/helperFuncs.js";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const pool = mysql.createPool({
|
const pool = mysql.createPool({
|
||||||
host: process.env.DB_HOST!,
|
host: process.env.DB_HOST!,
|
||||||
user: process.env.DB_USER!,
|
user: process.env.DB_USER!,
|
||||||
password: process.env.DB_PASSWORD!,
|
password: process.env.DB_PASSWORD!,
|
||||||
database: process.env.DB_NAME!,
|
database: process.env.DB_NAME!,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const allStorages = async () => {
|
export const allStorages = async () => {
|
||||||
const [result] = await pool.query<RowDataPacket[]>(
|
const [result] = await pool.query<RowDataPacket[]>(
|
||||||
"SELECT BIN_TO_UUID(uuid) AS uuid, name, description, created_at, updated_at FROM storage_locations;",
|
"SELECT BIN_TO_UUID(uuid) AS uuid, name, description, created_at, updated_at FROM storage_locations;",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SS001",
|
code: "SS001",
|
||||||
data: result,
|
data: result,
|
||||||
message: "Successfully fetched all storage locations.",
|
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: string, description: string) => {
|
export const newStorage = async (name: string, description: string) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
"INSERT INTO storage_locations (name, description) VALUES (?, ?)",
|
"INSERT INTO storage_locations (name, description) VALUES (?, ?)",
|
||||||
[name, description],
|
[name, description],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SS002",
|
code: "SS002",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Successfully created new storage location.",
|
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 (
|
export const updateStorage = async (
|
||||||
uuid: string,
|
uuid: string,
|
||||||
values: { name: string; description: string },
|
values: { name: string; description: string },
|
||||||
) => {
|
) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
"UPDATE storage_locations SET name = ?, description = ? WHERE uuid = UUID_TO_BIN(?);",
|
"UPDATE storage_locations SET name = ?, description = ? WHERE uuid = UUID_TO_BIN(?);",
|
||||||
[values.name, values.description, uuid],
|
[values.name, values.description, uuid],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SS003",
|
code: "SS003",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Successfully updated storage location.",
|
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: string) => {
|
export const deleteStorage = async (uuid: string) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
"DELETE FROM storage_locations WHERE uuid = UUID_TO_BIN(?);",
|
"DELETE FROM storage_locations WHERE uuid = UUID_TO_BIN(?);",
|
||||||
[uuid],
|
[uuid],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SS004",
|
code: "SS004",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Successfully deleted storage location.",
|
message: "Successfully deleted storage location.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_DELETED);
|
return returnErrorCode(STORAGE_ERROR_CODE.STORAGE_NOT_DELETED);
|
||||||
};
|
};
|
||||||
@@ -1,132 +1,135 @@
|
|||||||
import mysql, {type ResultSetHeader, type RowDataPacket} from "mysql2/promise";
|
import mysql, {
|
||||||
|
type ResultSetHeader,
|
||||||
|
type RowDataPacket,
|
||||||
|
} from "mysql2/promise";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import {GENERAL_ERROR_CODE, USER_ERROR_CODE} from "@stockhome/shared";
|
import { GENERAL_ERROR_CODE, USER_ERROR_CODE } from "@stockhome/shared";
|
||||||
import {returnErrorCode} from "../../../services/helperFuncs.js";
|
import { returnErrorCode } from "../../../services/helperFuncs.js";
|
||||||
import type { AuthTokenPayload } from "../../../services/tokenService";
|
import type { AuthTokenPayload } from "../../../services/tokenService";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const pool = mysql.createPool({
|
const pool = mysql.createPool({
|
||||||
host: process.env.DB_HOST!,
|
host: process.env.DB_HOST!,
|
||||||
user: process.env.DB_USER!,
|
user: process.env.DB_USER!,
|
||||||
password: process.env.DB_PASSWORD!,
|
password: process.env.DB_PASSWORD!,
|
||||||
database: process.env.DB_NAME!,
|
database: process.env.DB_NAME!,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const findUser = async (username: string, password: string) => {
|
export const findUser = async (username: string, password: string) => {
|
||||||
const [result] = await pool.query<RowDataPacket[]>(
|
const [result] = await pool.query<RowDataPacket[]>(
|
||||||
"SELECT BIN_TO_UUID(uuid) AS uuid, username, first_name, last_name, email, is_admin, is_active, last_login FROM users WHERE username = ? AND password = ?;",
|
"SELECT BIN_TO_UUID(uuid) AS uuid, username, first_name, last_name, email, is_admin, is_active, last_login FROM users WHERE username = ? AND password = ?;",
|
||||||
[username, password],
|
[username, password],
|
||||||
);
|
);
|
||||||
|
|
||||||
const userRow = result[0];
|
const userRow = result[0];
|
||||||
|
|
||||||
if (!userRow) {
|
if (!userRow) {
|
||||||
return returnErrorCode(USER_ERROR_CODE.WRONG_USERNAME_PASSWORD);
|
return returnErrorCode(USER_ERROR_CODE.WRONG_USERNAME_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast DB row to AuthTokenPayload for token generation / typing
|
// Cast DB row to AuthTokenPayload for token generation / typing
|
||||||
const user = userRow as unknown as AuthTokenPayload;
|
const user = userRow as unknown as AuthTokenPayload;
|
||||||
|
|
||||||
if (!user.is_active) {
|
if (!user.is_active) {
|
||||||
return returnErrorCode(USER_ERROR_CODE.USER_IS_DEACTIVATED);
|
return returnErrorCode(USER_ERROR_CODE.USER_IS_DEACTIVATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SU001",
|
code: "SU001",
|
||||||
data: user,
|
data: user,
|
||||||
message: "Successfully found user.",
|
message: "Successfully found user.",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginUser = async (username: string) => {
|
export const loginUser = async (username: string) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
"UPDATE users SET last_login = NOW() WHERE username = ?;",
|
"UPDATE users SET last_login = NOW() WHERE username = ?;",
|
||||||
[username],
|
[username],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SU002",
|
code: "SU002",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Successfully logged in user.",
|
message: "Successfully logged in user.",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return returnErrorCode(GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR);
|
return returnErrorCode(GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateSettings = async (payload: {
|
export const updateSettings = async (payload: {
|
||||||
"app-name": string;
|
"app-name": string;
|
||||||
currency: string;
|
currency: string;
|
||||||
}) => {
|
}) => {
|
||||||
const appName = payload["app-name"];
|
const appName = payload["app-name"];
|
||||||
const currency = payload.currency;
|
const currency = payload.currency;
|
||||||
|
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
`UPDATE app_settings
|
`UPDATE app_settings
|
||||||
SET value = CASE name
|
SET value = CASE name
|
||||||
WHEN "app-name" THEN ?
|
WHEN "app-name" THEN ?
|
||||||
WHEN "currency" THEN ?
|
WHEN "currency" THEN ?
|
||||||
ELSE value
|
ELSE value
|
||||||
END
|
END
|
||||||
WHERE name IN ("app-name", "currency");`,
|
WHERE name IN ("app-name", "currency");`,
|
||||||
[appName, currency],
|
[appName, currency],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SU003",
|
code: "SU003",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Successfully updated settings.",
|
message: "Successfully updated settings.",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return returnErrorCode(GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR);
|
return returnErrorCode(GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSettings = async () => {
|
export const getSettings = async () => {
|
||||||
const [result] = await pool.query<RowDataPacket[]>(
|
const [result] = await pool.query<RowDataPacket[]>(
|
||||||
`SELECT *
|
`SELECT *
|
||||||
FROM app_settings;`,
|
FROM app_settings;`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SU004",
|
code: "SU004",
|
||||||
data: result,
|
data: result,
|
||||||
message: "Successfully fetched settings.",
|
message: "Successfully fetched settings.",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return returnErrorCode(GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR);
|
return returnErrorCode(GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const changePassword = async (
|
export const changePassword = async (
|
||||||
username: string,
|
username: string,
|
||||||
currentPasswordUser: string,
|
currentPasswordUser: string,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
) => {
|
) => {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
`UPDATE users
|
`UPDATE users
|
||||||
SET password = ?
|
SET password = ?
|
||||||
WHERE username = ?
|
WHERE username = ?
|
||||||
AND password = ?;`,
|
AND password = ?;`,
|
||||||
[newPassword, username, currentPasswordUser],
|
[newPassword, username, currentPasswordUser],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.affectedRows > 0) {
|
if (result.affectedRows > 0) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
code: "SU005",
|
code: "SU005",
|
||||||
data: null,
|
data: null,
|
||||||
message: "Successfully fetched settings.",
|
message: "Successfully fetched settings.",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return returnErrorCode(GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR);
|
return returnErrorCode(USER_ERROR_CODE.PASSWORD_CHANGE_FAILED);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,284 +1,284 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import {authenticate} from "../../services/tokenService";
|
import { authenticate } from "../../services/tokenService";
|
||||||
import {
|
import {
|
||||||
allProducts,
|
allProducts,
|
||||||
deleteProduct,
|
deleteProduct,
|
||||||
newProduct,
|
newProduct,
|
||||||
productDetails,
|
productDetails,
|
||||||
setAmount,
|
setAmount,
|
||||||
updateItem,
|
updateItem,
|
||||||
} from "./database/products.database";
|
} from "./database/products.database";
|
||||||
import {GENERAL_ERROR_CODE, PRODUCT_ERROR_CODE} from "@stockhome/shared";
|
import { GENERAL_ERROR_CODE, PRODUCT_ERROR_CODE } from "@stockhome/shared";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post("/new-product", authenticate, async (req, res) => {
|
router.post("/new-product", authenticate, async (req, res) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
price,
|
price,
|
||||||
amount,
|
amount,
|
||||||
storage_location,
|
storage_location,
|
||||||
expiry_date,
|
expiry_date,
|
||||||
bottling_date,
|
bottling_date,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
code: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
message: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await newProduct(
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
price,
|
|
||||||
amount,
|
|
||||||
storage_location,
|
|
||||||
expiry_date,
|
|
||||||
bottling_date,
|
|
||||||
);
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await newProduct(
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
price,
|
||||||
|
amount,
|
||||||
|
storage_location,
|
||||||
|
expiry_date,
|
||||||
|
bottling_date,
|
||||||
|
);
|
||||||
|
|
||||||
|
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) => {
|
router.get("/all-products", authenticate, async (req, res) => {
|
||||||
const result = await allProducts();
|
const result = await allProducts();
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1],
|
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],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/view", async (req, res) => {
|
router.get("/view", async (req, res) => {
|
||||||
const uuid = req.query.uuid;
|
const uuid = req.query.uuid;
|
||||||
|
|
||||||
if (typeof uuid !== "string") {
|
if (typeof uuid !== "string") {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0],
|
code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1],
|
message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put("/mutate/set-amount", authenticate, async (req, res) => {
|
router.put("/mutate/set-amount", authenticate, async (req, res) => {
|
||||||
const amount = req.query.amount;
|
const amount = req.query.amount;
|
||||||
const itemUUID = req.query.item;
|
const itemUUID = req.query.item;
|
||||||
|
|
||||||
if (typeof itemUUID !== "string" || typeof amount !== "number") {
|
if (typeof itemUUID !== "string" || typeof amount !== "number") {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0],
|
code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1],
|
message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await setAmount(itemUUID, amount);
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await setAmount(itemUUID, amount);
|
||||||
|
|
||||||
|
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", authenticate, async (req, res) => {
|
router.post("/mutate/update-item", authenticate, async (req, res) => {
|
||||||
const itemUUID = req.query.item;
|
const itemUUID = req.query.item;
|
||||||
const newValues = req.body;
|
const newValues = req.body;
|
||||||
|
|
||||||
if (typeof itemUUID !== "string" || !newValues) {
|
if (typeof itemUUID !== "string" || !newValues) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0],
|
code: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1],
|
message: PRODUCT_ERROR_CODE.INVALID_PARAMETERS[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await updateItem(itemUUID, newValues);
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateItem(itemUUID, newValues);
|
||||||
|
|
||||||
|
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) => {
|
router.post("/delete-selection", authenticate, async (req, res) => {
|
||||||
const uuidArray = req.body;
|
const uuidArray = req.body;
|
||||||
|
|
||||||
if (!Array.isArray(uuidArray) || uuidArray.length === 0) {
|
if (!Array.isArray(uuidArray) || uuidArray.length === 0) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
code: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: PRODUCT_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
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],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(202).json({
|
|
||||||
success: true,
|
|
||||||
code: "SP006",
|
|
||||||
data: null,
|
|
||||||
message: "All selected products deleted successfully.",
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(202).json({
|
||||||
|
success: true,
|
||||||
|
code: "SP006",
|
||||||
|
data: null,
|
||||||
|
message: "All selected products deleted successfully.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
+160
-155
@@ -1,184 +1,189 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import {authenticate} from "../../services/tokenService";
|
import { authenticate } from "../../services/tokenService";
|
||||||
import {allStorages, deleteStorage, newStorage, updateStorage,} from "./database/storage.database";
|
import {
|
||||||
import {GENERAL_ERROR_CODE, STORAGE_ERROR_CODE} from "@stockhome/shared";
|
allStorages,
|
||||||
|
deleteStorage,
|
||||||
|
newStorage,
|
||||||
|
updateStorage,
|
||||||
|
} from "./database/storage.database";
|
||||||
|
import { GENERAL_ERROR_CODE, STORAGE_ERROR_CODE } from "@stockhome/shared";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/all-storages", authenticate, async (req, res) => {
|
router.get("/all-storages", authenticate, async (req, res) => {
|
||||||
const result = await allStorages();
|
const result = await allStorages();
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR,
|
|
||||||
data: null,
|
|
||||||
message: "Unknown error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.code === "SS001") {
|
|
||||||
return res.status(200).json({
|
|
||||||
success: true,
|
|
||||||
code: result.code,
|
|
||||||
data: result.data,
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR,
|
||||||
data: null,
|
data: null,
|
||||||
message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1],
|
message: "Unknown error",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.code === "SS001") {
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
code: result.code,
|
||||||
|
data: result.data,
|
||||||
|
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) => {
|
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) {
|
if (!name) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: STORAGE_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
code: STORAGE_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: STORAGE_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
message: STORAGE_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description == "") {
|
|
||||||
desc = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await newStorage(name, desc);
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description == "") {
|
||||||
|
desc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await newStorage(name, desc);
|
||||||
|
|
||||||
|
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],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
router.post("/update-storage", authenticate, async (req, res) => {
|
||||||
const storageUUID = req.query.storageUUID;
|
const storageUUID = req.query.storageUUID;
|
||||||
const values = req.body;
|
const values = req.body;
|
||||||
|
|
||||||
if (!storageUUID || !values) {
|
if (!storageUUID || !values) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: STORAGE_ERROR_CODE.INVALID_PARAMETERS[0],
|
code: STORAGE_ERROR_CODE.INVALID_PARAMETERS[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: STORAGE_ERROR_CODE.INVALID_PARAMETERS[1],
|
message: STORAGE_ERROR_CODE.INVALID_PARAMETERS[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
router.delete("/delete", authenticate, async (req, res) => {
|
||||||
const uuid = req.query.uuid;
|
const uuid = req.query.uuid;
|
||||||
|
|
||||||
if (typeof uuid !== "string") {
|
if (typeof uuid !== "string") {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: STORAGE_ERROR_CODE.INVALID_PARAMETERS[0],
|
code: STORAGE_ERROR_CODE.INVALID_PARAMETERS[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: STORAGE_ERROR_CODE.INVALID_PARAMETERS[1],
|
message: STORAGE_ERROR_CODE.INVALID_PARAMETERS[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await deleteStorage(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 === "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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await deleteStorage(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 === "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],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
+219
-213
@@ -1,245 +1,251 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import {authenticate, generateToken} from "../../services/tokenService";
|
import { authenticate, generateToken } from "../../services/tokenService";
|
||||||
import {changePassword, findUser, getSettings, loginUser, updateSettings,} from "./database/users.database";
|
import {
|
||||||
import {GENERAL_ERROR_CODE, USER_ERROR_CODE} from "@stockhome/shared";
|
changePassword,
|
||||||
|
findUser,
|
||||||
|
getSettings,
|
||||||
|
loginUser,
|
||||||
|
updateSettings,
|
||||||
|
} from "./database/users.database";
|
||||||
|
import { GENERAL_ERROR_CODE, USER_ERROR_CODE } from "@stockhome/shared";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post("/verify-token", authenticate, async (req, res) => {
|
router.post("/verify-token", authenticate, async (req, res) => {
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/update-app-settings", authenticate, async (req, res) => {
|
router.post("/update-app-settings", authenticate, async (req, res) => {
|
||||||
const values = req.body;
|
const values = req.body;
|
||||||
|
|
||||||
if (!values || Object.keys(values).length === 0) {
|
if (!values || Object.keys(values).length === 0) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: USER_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
code: USER_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: USER_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
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: result.code,
|
|
||||||
data: result.data,
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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: result.code,
|
||||||
|
data: result.data,
|
||||||
|
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("/settings", authenticate, async (req, res) => {
|
router.get("/settings", authenticate, async (req, res) => {
|
||||||
const result = await getSettings();
|
const result = await getSettings();
|
||||||
|
|
||||||
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: result.code,
|
|
||||||
data: result.data,
|
|
||||||
message: result.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1],
|
message: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1],
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.code === "SU004") {
|
||||||
|
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 || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
||||||
|
data: null,
|
||||||
|
message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/login", async (req, res) => {
|
router.post("/login", async (req, res) => {
|
||||||
const username = req.body.username;
|
const username = req.body.username;
|
||||||
const password = req.body.password;
|
const password = req.body.password;
|
||||||
|
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: USER_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
code: USER_ERROR_CODE.INVALID_REQUEST_BODY[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: USER_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
message: USER_ERROR_CODE.INVALID_REQUEST_BODY[1],
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await findUser(username, password);
|
|
||||||
|
|
||||||
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,
|
|
||||||
data: null,
|
|
||||||
message: result.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.code === USER_ERROR_CODE.USER_IS_DEACTIVATED[0]) {
|
|
||||||
return res.status(403).json({
|
|
||||||
success: false,
|
|
||||||
code: result.code,
|
|
||||||
data: null,
|
|
||||||
message: result.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.code === "SU001") {
|
|
||||||
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 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",
|
|
||||||
data: {
|
|
||||||
token,
|
|
||||||
},
|
|
||||||
message: "User token generated successfully",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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],
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await findUser(username, password);
|
||||||
|
|
||||||
|
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,
|
||||||
|
data: null,
|
||||||
|
message: result.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.code === USER_ERROR_CODE.USER_IS_DEACTIVATED[0]) {
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
code: result.code,
|
||||||
|
data: null,
|
||||||
|
message: result.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.code === "SU001") {
|
||||||
|
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 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",
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
message: "User token generated successfully",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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("/change-password", authenticate, async (req, res) => {
|
router.post("/change-password", authenticate, async (req, res) => {
|
||||||
const currentPassword = req.body.currentPassword;
|
const currentPassword = req.body.currentPassword;
|
||||||
const newPassword = req.body.newPassword;
|
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) {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!req.user) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
code: result.code || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
code: GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: result.message || GENERAL_ERROR_CODE.UNEXPECTED_SERVER_ERROR[1],
|
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) {
|
||||||
|
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 || 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;
|
||||||
+89
-86
@@ -1,10 +1,9 @@
|
|||||||
import type {NextFunction, Request, Response} from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import mysql, {type ResultSetHeader, type RowDataPacket} from "mysql2";
|
import mysql, { type ResultSetHeader, type RowDataPacket } from "mysql2";
|
||||||
import {readFile} from "fs/promises";
|
import { readFile } from "fs/promises"; // frontend routes
|
||||||
// frontend routes
|
|
||||||
import userRouter from "./routes/app/users.route";
|
import userRouter from "./routes/app/users.route";
|
||||||
import productRouter from "./routes/app/products.route";
|
import productRouter from "./routes/app/products.route";
|
||||||
import storageRouter from "./routes/app/storage.route";
|
import storageRouter from "./routes/app/storage.route";
|
||||||
@@ -18,16 +17,16 @@ const port = 8004;
|
|||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({extended: true}));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
const pool = mysql
|
const pool = mysql
|
||||||
.createPool({
|
.createPool({
|
||||||
host: process.env.DB_HOST!,
|
host: process.env.DB_HOST!,
|
||||||
user: process.env.DB_USER!,
|
user: process.env.DB_USER!,
|
||||||
password: process.env.DB_PASSWORD!,
|
password: process.env.DB_PASSWORD!,
|
||||||
database: process.env.DB_NAME!,
|
database: process.env.DB_NAME!,
|
||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
|
|
||||||
app.use("/users", userRouter);
|
app.use("/users", userRouter);
|
||||||
|
|
||||||
@@ -36,113 +35,117 @@ app.use("/products", productRouter);
|
|||||||
app.use("/storage", storageRouter);
|
app.use("/storage", storageRouter);
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
runStartup(port);
|
runStartup(port);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Startup code
|
// Startup code
|
||||||
const runStartup = async (port: number) => {
|
const runStartup = async (port: number) => {
|
||||||
let firstStartupValue: string | null = null;
|
let firstStartupValue: string | null = null;
|
||||||
try {
|
try {
|
||||||
const [firstResponse] = await pool.query<RowDataPacket[]>(
|
const [firstResponse] = await pool.query<RowDataPacket[]>(
|
||||||
`SELECT value
|
`SELECT value
|
||||||
FROM app_settings
|
FROM app_settings
|
||||||
WHERE name = "first-startup";`,
|
WHERE name = "first-startup";`,
|
||||||
);
|
);
|
||||||
firstStartupValue = firstResponse[0]?.value ?? null;
|
firstStartupValue = firstResponse[0]?.value ?? null;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof Error && (err as NodeJS.ErrnoException & { code?: string }).code !== "ER_NO_SUCH_TABLE") {
|
if (
|
||||||
throw err;
|
err instanceof Error &&
|
||||||
}
|
(err as NodeJS.ErrnoException & { code?: string }).code !==
|
||||||
|
"ER_NO_SUCH_TABLE"
|
||||||
|
) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstStartupValue !== "false") {
|
||||||
|
const schemaPath = path.join(__dirname, "database.scheme.sql");
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstStartupValue !== "false") {
|
const [result] = await pool.query<RowDataPacket[]>(
|
||||||
const schemaPath = path.join(__dirname, "database.scheme.sql");
|
`SELECT value
|
||||||
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
|
FROM app_settings
|
||||||
WHERE name = "first-startup";`,
|
WHERE name = "first-startup";`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result[0]?.value === "true") {
|
if (result[0]?.value === "true") {
|
||||||
const insertResult = await insertFirstData();
|
const insertResult = await insertFirstData();
|
||||||
|
|
||||||
if (insertResult.affectedRows > 0) {
|
if (insertResult.affectedRows > 0) {
|
||||||
console.log("Successfully created admin user!");
|
console.log("Successfully created admin user!");
|
||||||
console.log("Username: admin");
|
console.log("Username: admin");
|
||||||
console.log("Password: admin");
|
console.log("Password: admin");
|
||||||
|
|
||||||
const [scndResponse] = await pool.query<ResultSetHeader>(
|
const [scndResponse] = await pool.query<ResultSetHeader>(
|
||||||
`UPDATE app_settings
|
`UPDATE app_settings
|
||||||
SET value = "false"
|
SET value = "false"
|
||||||
WHERE name = "first-startup";`,
|
WHERE name = "first-startup";`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (scndResponse.affectedRows > 0) {
|
if (scndResponse.affectedRows > 0) {
|
||||||
console.log("Database settet up successfully!");
|
console.log("Database settet up successfully!");
|
||||||
} else {
|
} else {
|
||||||
console.error("There was an error while setting up the database!");
|
console.error("There was an error while setting up the database!");
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("Error while creating admin user.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Error while creating admin user.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Everything is settet up successfully!");
|
console.log("Everything is settet up successfully!");
|
||||||
console.log(`Backend is running on http://localhost:${port}`);
|
console.log(`Backend is running on http://localhost:${port}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const insertFirstData = async (): Promise<ResultSetHeader> => {
|
const insertFirstData = async (): Promise<ResultSetHeader> => {
|
||||||
const [insertResult] = await pool.query<ResultSetHeader>(
|
const [insertResult] = await pool.query<ResultSetHeader>(
|
||||||
`INSERT INTO users (username, first_name, last_name, email, password, is_admin)
|
`INSERT INTO users (username, first_name, last_name, email, password, is_admin)
|
||||||
VALUES ("admin", "admin", "admin", "admin@example.com", "admin", 1)`,
|
VALUES ("admin", "admin", "admin", "admin@example.com", "admin", 1)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO storage_locations (name, description)
|
`INSERT INTO storage_locations (name, description)
|
||||||
VALUES (?, ?);`,
|
VALUES (?, ?);`,
|
||||||
["Default Storage", "Initial storage location"],
|
["Default Storage", "Initial storage location"],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [storageRows] = await pool.query<RowDataPacket[]>(
|
const [storageRows] = await pool.query<RowDataPacket[]>(
|
||||||
`SELECT uuid
|
`SELECT uuid
|
||||||
FROM storage_locations
|
FROM storage_locations
|
||||||
WHERE name = ? LIMIT 1;`,
|
WHERE name = ? LIMIT 1;`,
|
||||||
["Default Storage"],
|
["Default Storage"],
|
||||||
);
|
);
|
||||||
|
|
||||||
const storageUuid: string | null = storageRows[0]?.uuid ?? null;
|
const storageUuid: string | null = storageRows[0]?.uuid ?? null;
|
||||||
if (storageUuid) {
|
if (storageUuid) {
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO products (name, description, price, amount, storage_location, picture)
|
`INSERT INTO products (name, description, price, amount, storage_location, picture)
|
||||||
VALUES (?, ?, ?, ?, ?, ?);`,
|
VALUES (?, ?, ?, ?, ?, ?);`,
|
||||||
[
|
[
|
||||||
"Welcome Product",
|
"Welcome Product",
|
||||||
"Your first item in Stockhome",
|
"Your first item in Stockhome",
|
||||||
"0.00",
|
"0.00",
|
||||||
1,
|
1,
|
||||||
storageUuid,
|
storageUuid,
|
||||||
null,
|
null,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Welcome product is ready...");
|
console.log("Welcome product is ready...");
|
||||||
return insertResult;
|
return insertResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
// error handling code
|
// error handling code
|
||||||
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
res.status(500).send("Something broke!");
|
res.status(500).send("Something broke!");
|
||||||
});
|
});
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import type {ErrorCode} from "../misc/types.ts";
|
import type { ErrorCode } from "../misc/types.ts";
|
||||||
|
|
||||||
export const returnErrorCode = (errorKey: ErrorCode) => {
|
export const returnErrorCode = (errorKey: ErrorCode) => {
|
||||||
if (!Array.isArray(errorKey) || errorKey.length < 2) {
|
if (!Array.isArray(errorKey) || errorKey.length < 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
code: errorKey[0],
|
code: errorKey[0],
|
||||||
data: null,
|
data: null,
|
||||||
message: errorKey[1]
|
message: errorKey[1],
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
@@ -1,36 +1,40 @@
|
|||||||
import type {NextFunction, Request, Response} from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
import {type JWTPayload, jwtVerify, SignJWT} from "jose";
|
import { type JWTPayload, jwtVerify, SignJWT } from "jose";
|
||||||
import env from "dotenv";
|
import env from "dotenv";
|
||||||
|
|
||||||
env.config();
|
env.config();
|
||||||
const secret = new TextEncoder().encode(process.env.SECRET_KEY);
|
const secret = new TextEncoder().encode(process.env.SECRET_KEY);
|
||||||
|
|
||||||
export type AuthTokenPayload = JWTPayload & {
|
export type AuthTokenPayload = JWTPayload & {
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare module "express-serve-static-core" {
|
declare module "express-serve-static-core" {
|
||||||
interface Request {
|
interface Request {
|
||||||
user?: AuthTokenPayload;
|
user?: AuthTokenPayload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateToken(payload: AuthTokenPayload) {
|
export async function generateToken(payload: AuthTokenPayload) {
|
||||||
return await new SignJWT(payload)
|
return await new SignJWT(payload)
|
||||||
.setProtectedHeader({alg: "HS256"})
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
.setExpirationTime("24h")
|
.setExpirationTime("24h")
|
||||||
.sign(secret);
|
.sign(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function authenticate(req: Request, res: Response, next: NextFunction) {
|
export async function authenticate(
|
||||||
const authHeader = req.headers["authorization"];
|
req: Request,
|
||||||
const token = authHeader && authHeader.split(" ")[1];
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
) {
|
||||||
|
const authHeader = req.headers["authorization"];
|
||||||
|
const token = authHeader && authHeader.split(" ")[1];
|
||||||
|
|
||||||
if (token == null) return res.sendStatus(401);
|
if (token == null) return res.sendStatus(401);
|
||||||
|
|
||||||
const {payload} = await jwtVerify<AuthTokenPayload>(token, secret);
|
const { payload } = await jwtVerify<AuthTokenPayload>(token, secret);
|
||||||
req.user = payload;
|
req.user = payload;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
@@ -9,17 +9,13 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"ignoreDeprecations": "6.0",
|
"ignoreDeprecations": "6.0",
|
||||||
"types": [
|
"types": ["node"],
|
||||||
"node"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["./**/*.ts"],
|
||||||
"./**/*.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
+22
-22
@@ -1,29 +1,29 @@
|
|||||||
import {defineConfig} from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import path from 'path'
|
import path from "path";
|
||||||
import {dirname} from "node:path"
|
import { dirname } from "node:path";
|
||||||
import {fileURLToPath} from "node:url"
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
root: 'src',
|
root: "src",
|
||||||
build: {
|
build: {
|
||||||
outDir: '../dist',
|
outDir: "../dist",
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
minify: 'esbuild',
|
minify: "esbuild",
|
||||||
assetsDir: 'assets',
|
assetsDir: "assets",
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: path.resolve(__dirname, 'src/index.html'),
|
input: path.resolve(__dirname, "src/index.html"),
|
||||||
output: {
|
output: {
|
||||||
assetFileNames: 'assets/[name]-[hash][extname]',
|
assetFileNames: "assets/[name]-[hash][extname]",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
resolve: {
|
},
|
||||||
alias: {
|
resolve: {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
alias: {
|
||||||
},
|
"@": path.resolve(__dirname, "src"),
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { Input, Button, Alert } from "@mui/joy";
|
import { Alert, Button, Input } from "@mui/joy";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { signInUser } from "../utils/api/auth";
|
import { signInUser } from "../utils/api/auth";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ExitToAppIcon from "@mui/icons-material/ExitToApp";
|
|||||||
import TranslateIcon from "@mui/icons-material/Translate";
|
import TranslateIcon from "@mui/icons-material/Translate";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useNavigate, useMatchRoute } from "@tanstack/react-router";
|
import { useMatchRoute, useNavigate } from "@tanstack/react-router";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { changeTranslation } from "../utils/uxFncs";
|
import { changeTranslation } from "../utils/uxFncs";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useQueryClient, useMutation } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { deleteStorage, updateStorage } from "../utils/api/storages";
|
import { deleteStorage, updateStorage } from "../utils/api/storages";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { useStore } from "@tanstack/react-store";
|
import { useStore } from "@tanstack/react-store";
|
||||||
import { Input, Button } from "@mui/joy";
|
import { Button, Input } from "@mui/joy";
|
||||||
import type { Storage } from "../misc/interfaces";
|
import type { Storage } from "../misc/interfaces";
|
||||||
import { formatDate } from "../utils/uxFncs";
|
import { formatDate } from "../utils/uxFncs";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
DialogTitle,
|
|
||||||
DialogContent,
|
|
||||||
Stack,
|
Stack,
|
||||||
Input,
|
|
||||||
Button,
|
|
||||||
Alert,
|
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { NewStorage, AlertInterface } from "../../misc/interfaces";
|
import type { AlertInterface, NewStorage } from "../../misc/interfaces";
|
||||||
import { mutateNewStorage } from "../../utils/api/storages";
|
import { mutateNewStorage } from "../../utils/api/storages";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
DialogTitle,
|
||||||
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
DialogTitle,
|
|
||||||
Stack,
|
Stack,
|
||||||
Input,
|
|
||||||
Button,
|
|
||||||
Alert,
|
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { ChangePasswordIntf, AlertInterface } from "../../misc/interfaces";
|
import type { AlertInterface, ChangePasswordIntf } from "../../misc/interfaces";
|
||||||
import { mutatePassword } from "../../utils/api/auth";
|
import { mutatePassword } from "../../utils/api/auth";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { USER_ERROR_CODE } from "@stockhome/shared";
|
||||||
|
|
||||||
interface ChangePasswordProps {
|
interface ChangePasswordProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setOpen: (value: boolean) => void;
|
setOpen: (value: boolean) => void;
|
||||||
|
alert: (alert: AlertInterface) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
||||||
@@ -35,7 +37,18 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
newPasswordRep: "",
|
newPasswordRep: "",
|
||||||
},
|
},
|
||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
mutate(value);
|
console.log(value);
|
||||||
|
|
||||||
|
if (value.newPassword === value.newPasswordRep) {
|
||||||
|
mutate(value);
|
||||||
|
} else {
|
||||||
|
setAlert({
|
||||||
|
isAlert: true,
|
||||||
|
type: "danger",
|
||||||
|
header: t("error"),
|
||||||
|
text: t(USER_ERROR_CODE.PASSWORDS_NOT_MATCHED[0]),
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,6 +63,21 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
// Sets the success alert in the settings page via component props
|
||||||
|
props.alert({
|
||||||
|
isAlert: true,
|
||||||
|
type: "success",
|
||||||
|
header: t("success"),
|
||||||
|
text: t("SU005"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sets the success alert locally (in the modal itself)
|
||||||
|
setAlert({
|
||||||
|
isAlert: true,
|
||||||
|
type: "success",
|
||||||
|
header: t("success"),
|
||||||
|
text: t("SU005"),
|
||||||
|
});
|
||||||
props.setOpen(false);
|
props.setOpen(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import { useForm } from "@tanstack/react-form";
|
|||||||
import { createProduct } from "../utils/api/products";
|
import { createProduct } from "../utils/api/products";
|
||||||
import { getStorages } from "../utils/api/storages";
|
import { getStorages } from "../utils/api/storages";
|
||||||
import type {
|
import type {
|
||||||
ProductFormValues,
|
|
||||||
AlertInterface,
|
AlertInterface,
|
||||||
|
ProductFormValues,
|
||||||
Storage,
|
Storage,
|
||||||
} from "../misc/interfaces";
|
} from "../misc/interfaces";
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
@@ -272,10 +272,11 @@ export const AddProduct = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex gap-3 items-center">
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography level="body-sm" className="text-slate-500">
|
||||||
{t("product-details")}
|
{t("product-details")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<div className="grow"></div>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Typography,
|
Alert,
|
||||||
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Chip,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Sheet,
|
Sheet,
|
||||||
Table,
|
Table,
|
||||||
Avatar,
|
Typography,
|
||||||
Chip,
|
|
||||||
Checkbox,
|
|
||||||
Alert,
|
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -17,8 +17,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { deleteSelectedProducts, getProducts } from "../utils/api/products";
|
import { deleteSelectedProducts, getProducts } from "../utils/api/products";
|
||||||
import { formatDate } from "../utils/uxFncs";
|
import { formatDate } from "../utils/uxFncs";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import type { ProductRow } from "../misc/interfaces";
|
import type { AlertInterface, ProductRow } from "../misc/interfaces";
|
||||||
import type { AlertInterface } from "../misc/interfaces";
|
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
|
|
||||||
export const InventoryPage = () => {
|
export const InventoryPage = () => {
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { getStorages } from "../utils/api/storages.ts";
|
import { getStorages } from "../utils/api/storages.ts";
|
||||||
import {
|
import {
|
||||||
CircularProgress,
|
Alert,
|
||||||
Typography,
|
Box,
|
||||||
Select,
|
|
||||||
Option,
|
|
||||||
Input,
|
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
|
CircularProgress,
|
||||||
Divider,
|
Divider,
|
||||||
Box,
|
Input,
|
||||||
Alert,
|
Option,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { mutateProduct, getProductDetails } from "../utils/api/products.ts";
|
import { getProductDetails, mutateProduct } from "../utils/api/products.ts";
|
||||||
import { toInputDate } from "../utils/uxFncs";
|
import { toInputDate } from "../utils/uxFncs";
|
||||||
import type {
|
import type {
|
||||||
ProductFormValues,
|
|
||||||
productDetailsInterface,
|
|
||||||
AlertInterface,
|
AlertInterface,
|
||||||
|
productDetailsInterface,
|
||||||
|
ProductFormValues,
|
||||||
Storage,
|
Storage,
|
||||||
} from "../misc/interfaces";
|
} from "../misc/interfaces";
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
Input,
|
|
||||||
Button,
|
|
||||||
CircularProgress,
|
|
||||||
Typography,
|
|
||||||
Sheet,
|
|
||||||
Chip,
|
|
||||||
Divider,
|
|
||||||
Alert,
|
Alert,
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
CircularProgress,
|
||||||
|
Divider,
|
||||||
|
Input,
|
||||||
|
Sheet,
|
||||||
|
Typography,
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { SettingsIntf } from "../misc/interfaces";
|
import type { AlertInterface, SettingsIntf } from "../misc/interfaces";
|
||||||
import type { AlertInterface } from "../misc/interfaces";
|
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
import { mutateSettings, fetchSettings } from "../utils/api/settings";
|
import { fetchSettings, mutateSettings } from "../utils/api/settings";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ChangePasswordModal } from "../components/modals/ChangePasswordModal";
|
import { ChangePasswordModal } from "../components/modals/ChangePasswordModal";
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ export const Settings = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChangePasswordModal isOpen={modal} setOpen={setModal} />
|
<ChangePasswordModal alert={setAlert} isOpen={modal} setOpen={setModal} />
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getStorages } from "../utils/api/storages";
|
import { getStorages } from "../utils/api/storages";
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Alert,
|
||||||
Table,
|
|
||||||
Button,
|
Button,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
Sheet,
|
||||||
|
Table,
|
||||||
Typography,
|
Typography,
|
||||||
Alert,
|
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { Storage } from "../misc/interfaces";
|
import type { AlertInterface, Storage } from "../misc/interfaces";
|
||||||
import type { AlertInterface } from "../misc/interfaces";
|
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
import { StorageRow } from "../components/StorageRow";
|
import { StorageRow } from "../components/StorageRow";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { getStorages } from "../utils/api/storages.ts";
|
import { getStorages } from "../utils/api/storages.ts";
|
||||||
import {
|
import {
|
||||||
CircularProgress,
|
Alert,
|
||||||
Typography,
|
Box,
|
||||||
Select,
|
|
||||||
Option,
|
|
||||||
Input,
|
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
|
CircularProgress,
|
||||||
Divider,
|
Divider,
|
||||||
Box,
|
Input,
|
||||||
Alert,
|
Option,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { mutateProduct, getProductDetails } from "../utils/api/products.ts";
|
import { getProductDetails, mutateProduct } from "../utils/api/products.ts";
|
||||||
import { toInputDate } from "../utils/uxFncs";
|
import { toInputDate } from "../utils/uxFncs";
|
||||||
import type {
|
import type {
|
||||||
ProductFormValues,
|
|
||||||
productDetailsInterface,
|
|
||||||
AlertInterface,
|
AlertInterface,
|
||||||
|
productDetailsInterface,
|
||||||
|
ProductFormValues,
|
||||||
Storage,
|
Storage,
|
||||||
} from "../misc/interfaces";
|
} from "../misc/interfaces";
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
@@ -342,28 +342,32 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div>
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<div className="flex gap-3 items-center">
|
||||||
{t("product-details")}
|
<Typography level="body-sm" className="text-slate-500">
|
||||||
</Typography>
|
{t("product-details")}
|
||||||
<Button
|
</Typography>
|
||||||
startDecorator={<QrCodeIcon />}
|
<div className="grow"></div>
|
||||||
loading={isPending}
|
<Button
|
||||||
onClick={() => downloadQRcode()}
|
startDecorator={<QrCodeIcon />}
|
||||||
size="lg"
|
loading={isPending}
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
onClick={() => downloadQRcode()}
|
||||||
>
|
size="lg"
|
||||||
{t("download-qr-code")}
|
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
||||||
</Button>
|
>
|
||||||
<Button
|
{t("download-qr-code")}
|
||||||
type="submit"
|
</Button>
|
||||||
loading={isPending}
|
<Button
|
||||||
size="lg"
|
type="submit"
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
loading={isPending}
|
||||||
>
|
size="lg"
|
||||||
{t("save")}
|
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
||||||
</Button>
|
>
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{alert.isAlert && (
|
{alert.isAlert && (
|
||||||
<Alert
|
<Alert
|
||||||
color={alert.type}
|
color={alert.type}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"success": "Erfolg",
|
"success": "Erfolg",
|
||||||
"submit": "Absenden",
|
"submit": "Absenden",
|
||||||
|
"SU005": "Das Passwort wurde erfolgreich geändert!",
|
||||||
"EG001": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.",
|
"EG001": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.",
|
||||||
"EU001": "Falscher Benutzername oder Passwort!",
|
"EU001": "Falscher Benutzername oder Passwort!",
|
||||||
"EU002": "Ihr Konto wurde deaktiviert. Bitte wenden Sie sich an einen Administrator.",
|
"EU002": "Ihr Konto wurde deaktiviert. Bitte wenden Sie sich an einen Administrator.",
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
"EU007": "Passwort konnte nicht geändert werden. Bitte versuchen Sie es erneut.",
|
"EU007": "Passwort konnte nicht geändert werden. Bitte versuchen Sie es erneut.",
|
||||||
"EU008": "Ihre Einstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.",
|
"EU008": "Ihre Einstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.",
|
||||||
"EU009": "Ihre Einstellungen konnten nicht geladen werden. Bitte versuchen Sie es erneut.",
|
"EU009": "Ihre Einstellungen konnten nicht geladen werden. Bitte versuchen Sie es erneut.",
|
||||||
|
"EU010": "Sie müssen Ihr neues Passwort zweimal korrekt eingeben.",
|
||||||
"ES001": "Bitte füllen Sie alle erforderlichen Felder für den Lagerort aus.",
|
"ES001": "Bitte füllen Sie alle erforderlichen Felder für den Lagerort aus.",
|
||||||
"ES002": "Einige erforderliche Lagerinformationen fehlen. Bitte überprüfen Sie Ihre Eingabe.",
|
"ES002": "Einige erforderliche Lagerinformationen fehlen. Bitte überprüfen Sie Ihre Eingabe.",
|
||||||
"ES003": "Der Lagerort konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.",
|
"ES003": "Der Lagerort konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
|
"SU005": "Your password is updated successfully!",
|
||||||
"EG001": "An unexpected error occurred. Please try again later.",
|
"EG001": "An unexpected error occurred. Please try again later.",
|
||||||
"EU001": "Wrong username or password!",
|
"EU001": "Wrong username or password!",
|
||||||
"EU002": "Your account has been deactivated. Please contact an administrator.",
|
"EU002": "Your account has been deactivated. Please contact an administrator.",
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
"EU007": "Failed to change your password. Please try again.",
|
"EU007": "Failed to change your password. Please try again.",
|
||||||
"EU008": "Your settings could not be saved. Please try again.",
|
"EU008": "Your settings could not be saved. Please try again.",
|
||||||
"EU009": "Your settings could not be loaded. Please try again.",
|
"EU009": "Your settings could not be loaded. Please try again.",
|
||||||
|
"EU010": "You must enter your new password twice correctly.",
|
||||||
"ES001": "Please fill in all required fields for the storage location.",
|
"ES001": "Please fill in all required fields for the storage location.",
|
||||||
"ES002": "Some required storage information is missing. Please check your input.",
|
"ES002": "Some required storage information is missing. Please check your input.",
|
||||||
"ES003": "The storage location could not be updated. Please try again.",
|
"ES003": "The storage location could not be updated. Please try again.",
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -9,12 +9,15 @@
|
|||||||
"version": "0.1.0-dev",
|
"version": "0.1.0-dev",
|
||||||
"description": "",
|
"description": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build:shared": "npm run build --workspace=@stockhome/shared",
|
||||||
|
"build:backend": "npm run build --workspace=backend",
|
||||||
|
"build:frontend": "npm run build --workspace=frontend",
|
||||||
"dev:frontend": "npm run dev --workspace=frontend",
|
"dev:frontend": "npm run dev --workspace=frontend",
|
||||||
"dev:backend": "npm run dev --workspace=backend",
|
"dev:backend": "npm run build:shared && npm run dev --workspace=backend",
|
||||||
"dev:docker": "docker compose -f docker-compose.dev.yml up -d --wait",
|
"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": "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": "npm run build --workspace=frontend && npm run build --workspace=backend"
|
"buildProd": "npm run build:shared && npm run build:backend && npm run build:frontend"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
+12
-4
@@ -2,14 +2,22 @@
|
|||||||
"name": "@stockhome/shared",
|
"name": "@stockhome/shared",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "commonjs",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "barrelsby --directory src --delete --single",
|
"prebuild": "barrelsby --directory src --delete --single",
|
||||||
"build": "tsc -b",
|
"build:esm": "tsc -p tsconfig.esm.json",
|
||||||
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
||||||
|
"build": "npm run prebuild && npm run build:esm && npm run build:cjs",
|
||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"main": "./dist/src/index.js",
|
"exports": {
|
||||||
"types": "./dist/src/index.d.ts",
|
".": {
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"import": "./dist/esm/index.js",
|
||||||
|
"require": "./dist/cjs/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"barrelsby": "^2.8.1",
|
"barrelsby": "^2.8.1",
|
||||||
"typescript": "~6.0.2"
|
"typescript": "~6.0.2"
|
||||||
|
|||||||
@@ -1,35 +1,42 @@
|
|||||||
export const GENERAL_ERROR_CODE = {
|
export const GENERAL_ERROR_CODE = {
|
||||||
UNEXPECTED_SERVER_ERROR: ["EG001", "Unexpected server error"],
|
UNEXPECTED_SERVER_ERROR: ["EG001", "Unexpected server error"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const USER_ERROR_CODE = {
|
export const USER_ERROR_CODE = {
|
||||||
WRONG_USERNAME_PASSWORD: ["EU001", "Username or password is wrong"],
|
WRONG_USERNAME_PASSWORD: ["EU001", "Username or password is wrong"],
|
||||||
USER_IS_DEACTIVATED: ["EU002", "User is deactivated"],
|
USER_IS_DEACTIVATED: ["EU002", "User is deactivated"],
|
||||||
INVALID_REQUEST_BODY: ["EU003", "Invalid request body. Username and password are required."],
|
INVALID_REQUEST_BODY: [
|
||||||
INVALID_PARAMETERS: ["EU004", "Invalid or missing parameters."],
|
"EU003",
|
||||||
TOKEN_GENERATION_FAILED: ["EU005", "Failed to generate user token."],
|
"Invalid request body. Username and password are required.",
|
||||||
LOGIN_FAILED: ["EU006", "Failed to log in user."],
|
],
|
||||||
PASSWORD_CHANGE_FAILED: ["EU007", "Failed to change password."],
|
INVALID_PARAMETERS: ["EU004", "Invalid or missing parameters."],
|
||||||
SETTINGS_NOT_UPDATED: ["EU008", "Settings could not be updated."],
|
TOKEN_GENERATION_FAILED: ["EU005", "Failed to generate user token."],
|
||||||
SETTINGS_NOT_FOUND: ["EU009", "Settings could not be retrieved."],
|
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."],
|
||||||
|
PASSWORDS_NOT_MATCHED: ["EU010", "The two passwords are not matching."],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const STORAGE_ERROR_CODE = {
|
export const STORAGE_ERROR_CODE = {
|
||||||
INVALID_REQUEST_BODY: ["ES001", "Invalid request body"],
|
INVALID_REQUEST_BODY: ["ES001", "Invalid request body"],
|
||||||
INVALID_PARAMETERS: ["ES002", "Parameter storageUUID or/and parameter values are/is missing."],
|
INVALID_PARAMETERS: [
|
||||||
STORAGE_NOT_UPDATED: ["ES003", "Storage is not updated."],
|
"ES002",
|
||||||
STORAGE_NOT_DELETED: ["ES004", "Storage is not deleted."],
|
"Parameter storageUUID or/and parameter values are/is missing.",
|
||||||
NO_STORAGE_LOCATIONS_FOUND: ["ES005", "No storage locations found"],
|
],
|
||||||
STORAGE_NOT_CREATED: ["ES006", "Storage is not created."],
|
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;
|
} as const;
|
||||||
|
|
||||||
export const PRODUCT_ERROR_CODE = {
|
export const PRODUCT_ERROR_CODE = {
|
||||||
PRODUCT_NOT_CREATED: ["EP001", "Error while creating product"],
|
PRODUCT_NOT_CREATED: ["EP001", "Error while creating product"],
|
||||||
NO_PRODUCTS_FOUND: ["EP002", "No products found"],
|
NO_PRODUCTS_FOUND: ["EP002", "No products found"],
|
||||||
PRODUCT_NOT_FOUND: ["EP003", "Product not found"],
|
PRODUCT_NOT_FOUND: ["EP003", "Product not found"],
|
||||||
PRODUCT_AMOUNT_NOT_UPDATED: ["EP004", "Error while updating product amount"],
|
PRODUCT_AMOUNT_NOT_UPDATED: ["EP004", "Error while updating product amount"],
|
||||||
PRODUCT_NOT_UPDATED: ["EP005", "Error while updating product"],
|
PRODUCT_NOT_UPDATED: ["EP005", "Error while updating product"],
|
||||||
PRODUCT_NOT_DELETED: ["EP006", "Error while deleting product"],
|
PRODUCT_NOT_DELETED: ["EP006", "Error while deleting product"],
|
||||||
INVALID_REQUEST_BODY: ["EP007", "Invalid request body. Name is required."],
|
INVALID_REQUEST_BODY: ["EP007", "Invalid request body. Name is required."],
|
||||||
INVALID_PARAMETERS: ["EP008", "Invalid or missing parameters."],
|
INVALID_PARAMETERS: ["EP008", "Invalid or missing parameters."],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"target": "ES2022",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/cjs",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"declaration": false,
|
||||||
|
"declarationMap": false,
|
||||||
|
"ignoreDeprecations": "6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"outDir": "./dist/esm",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "./dist/types",
|
||||||
|
"declarationMap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,5 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"ignoreDeprecations": "6.0"
|
"ignoreDeprecations": "6.0"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*.ts"]
|
||||||
"src/**/*.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user