Add auth server, login page, users table, create user script, etc

This commit is contained in:
2025-01-14 13:35:13 -05:00
parent fcfe4346f7
commit 2d849e34d1
15 changed files with 2487 additions and 184 deletions

22
ecosystem.config.js Normal file
View File

@@ -0,0 +1,22 @@
module.exports = {
apps: [
{
...commonSettings,
name: 'new-auth-server',
script: './inventory-server/auth/server.js',
env: {
NODE_ENV: 'production',
AUTH_PORT: 3011,
JWT_SECRET: process.env.JWT_SECRET
},
error_file: 'inventory-server/auth/logs/pm2/err.log',
out_file: 'inventory-server/auth/logs/pm2/out.log',
log_file: 'inventory-server/auth/logs/pm2/combined.log',
env_production: {
NODE_ENV: 'production',
AUTH_PORT: 3011,
JWT_SECRET: process.env.JWT_SECRET
}
}
]
};

View File

@@ -0,0 +1,41 @@
const bcrypt = require('bcrypt');
const mysql = require('mysql2/promise');
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});
require('dotenv').config({ path: '../.env' });
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
};
async function addUser() {
const username = await askQuestion('Enter username: ');
const password = await askQuestion('Enter password: ');
const hashedPassword = await bcrypt.hash(password, 10);
const connection = await mysql.createConnection(dbConfig);
try {
await connection.query('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword]);
console.log(`User ${username} added successfully.`);
} catch (error) {
console.error('Error adding user:', error);
} finally {
connection.end();
readline.close();
}
}
function askQuestion(query) {
return new Promise(resolve => readline.question(query, ans => {
resolve(ans);
}));
}
addUser();

1915
inventory-server/auth/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "auth-server",
"version": "1.0.0",
"description": "Authentication server for inventory management",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"add_user": "node add_user.js"
},
"dependencies": {
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}

View File

@@ -0,0 +1,6 @@
CREATE TABLE `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(255) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@@ -0,0 +1,135 @@
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const mysql = require('mysql2/promise');
require('dotenv').config({ path: '../.env' });
const app = express();
const PORT = process.env.AUTH_PORT || 3011;
// Database configuration
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
};
// Create a connection pool
const pool = mysql.createPool(dbConfig);
app.use(cors({
origin: [
'https://inventory.kent.pw',
'http://localhost:5173',
'http://127.0.0.1:5173',
/^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/,
/^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/
],
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
credentials: true,
exposedHeaders: ['set-cookie']
}));
app.use(express.json());
// Debug middleware to log request details
app.use((req, res, next) => {
console.log('Request details:', {
method: req.method,
url: req.url,
origin: req.get('Origin'),
headers: req.headers,
body: req.body,
});
next();
});
// Registration endpoint
app.post('/register', async (req, res) => {
try {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const connection = await pool.getConnection();
await connection.query('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword]);
connection.release();
res.status(201).json({ message: 'User registered successfully' });
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ error: 'Registration failed' });
}
});
// Login endpoint
app.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
console.log(`Login attempt for user: ${username}`);
const connection = await pool.getConnection();
const [rows] = await connection.query(
'SELECT * FROM users WHERE username = ?',
[username],
);
connection.release();
if (rows.length === 1) {
const user = rows[0];
const passwordMatch = await bcrypt.compare(password, user.password);
if (passwordMatch) {
console.log(`User ${username} authenticated successfully`);
const token = jwt.sign(
{ username: user.username },
process.env.JWT_SECRET,
{ expiresIn: '1h' },
);
res.json({ token });
} else {
console.error(`Invalid password for user: ${username}`);
res.status(401).json({ error: 'Invalid credentials' });
}
} else {
console.error(`User not found: ${username}`);
res.status(401).json({ error: 'Invalid credentials' });
}
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Login failed' });
}
});
// Protected endpoint example
app.get('/protected', async (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Optionally, you can fetch the user from the database here
// to verify that the user still exists or to get more user information
const connection = await pool.getConnection();
const [rows] = await connection.query('SELECT * FROM users WHERE username = ?', [decoded.username]);
connection.release();
if (rows.length === 0) {
return res.status(401).json({ error: 'User not found' });
}
res.json({ message: 'Protected resource accessed', user: decoded });
} catch (error) {
console.error('Protected endpoint error:', error);
res.status(403).json({ error: 'Invalid token' });
}
});
app.listen(PORT, "0.0.0.0", () => {
console.log(`Auth server running on port ${PORT}`);
});

View File

@@ -1,4 +1,4 @@
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { Routes, Route, useNavigate, Navigate } from 'react-router-dom';
import { MainLayout } from './components/layout/MainLayout'; import { MainLayout } from './components/layout/MainLayout';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Products } from './pages/Products'; import { Products } from './pages/Products';
@@ -8,29 +8,83 @@ import { Orders } from './pages/Orders';
import { Settings } from './pages/Settings'; import { Settings } from './pages/Settings';
import { Analytics } from './pages/Analytics'; import { Analytics } from './pages/Analytics';
import { Toaster } from '@/components/ui/sonner'; import { Toaster } from '@/components/ui/sonner';
import PurchaseOrders from './pages/PurchaseOrders'; import PurchaseOrders from './pages/PurchaseOrders';
import { Login } from './pages/Login';
import { useEffect } from 'react';
import config from './config';
const queryClient = new QueryClient(); const queryClient = new QueryClient();
function App() { function App() {
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
const navigate = useNavigate();
useEffect(() => {
const checkAuth = async () => {
const token = sessionStorage.getItem('token');
if (token) {
try {
const response = await fetch(`${config.authUrl}/protected`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
sessionStorage.removeItem('token');
sessionStorage.removeItem('isLoggedIn');
navigate('/login');
}
} catch (error) {
console.error('Token verification failed:', error);
sessionStorage.removeItem('token');
sessionStorage.removeItem('isLoggedIn');
navigate('/login');
}
}
};
checkAuth();
}, [navigate]);
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Router> <Toaster richColors position="top-center" />
<Toaster richColors position="top-center" /> <Routes>
<MainLayout> <Route path="/login" element={<Login />} />
<Routes> <Route
<Route path="/" element={<Dashboard />} /> path="/"
<Route path="/products" element={<Products />} /> element={
<Route path="/import" element={<Import />} /> isLoggedIn ? (
<Route path="/orders" element={<Orders />} /> <Navigate to="/dashboard" replace />
<Route path="/purchase-orders" element={<PurchaseOrders />} /> ) : (
<Route path="/analytics" element={<Analytics />} /> <Navigate to="/login" replace />
<Route path="/settings" element={<Settings />} /> )
</Routes> }
</MainLayout> />
</Router> <Route
path="*"
element={
isLoggedIn ? (
<MainLayout>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/products" element={<Products />} />
<Route path="/import" element={<Import />} />
<Route path="/orders" element={<Orders />} />
<Route path="/purchase-orders" element={<PurchaseOrders />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</MainLayout>
) : (
<Navigate to="/login" replace />
)
}
/>
</Routes>
</QueryClientProvider> </QueryClientProvider>
); );
} }
export default App; export default App;

View File

@@ -1,24 +1,118 @@
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; import { useState } from 'react';
import { AppSidebar } from "./AppSidebar"; import { NavLink, useLocation } from 'react-router-dom';
import {
LayoutDashboard,
Settings,
Package,
ShoppingBag,
PackagePlus,
BarChart4,
LogOut,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '../ui/button';
import { useNavigate } from 'react-router-dom';
interface MainLayoutProps { const links = [
children: React.ReactNode; { title: 'Dashboard', path: '/', icon: <LayoutDashboard /> },
} { title: 'Products', path: '/products', icon: <Package /> },
{ title: 'Import', path: '/import', icon: <PackagePlus /> },
{ title: 'Orders', path: '/orders', icon: <ShoppingBag /> },
{ title: 'Purchase Orders', path: '/purchase-orders', icon: <ShoppingBag /> },
{ title: 'Analytics', path: '/analytics', icon: <BarChart4 /> },
{ title: 'Settings', path: '/settings', icon: <Settings /> },
];
export function MainLayout({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(true);
const location = useLocation();
const navigate = useNavigate();
const handleLogout = () => {
sessionStorage.removeItem('isLoggedIn');
sessionStorage.removeItem('token');
navigate('/login');
};
export function MainLayout({ children }: MainLayoutProps) {
return ( return (
<SidebarProvider defaultOpen> <div className="flex h-screen">
<div className="flex min-h-screen w-full pr-2"> <aside
<AppSidebar /> className={cn(
<main className="flex-1 overflow-hidden"> 'border-r h-full transition-all duration-300 ease-in-out',
<div className="flex h-14 w-full items-center border-b px-4 gap-4"> open ? 'w-64' : 'w-20',
<SidebarTrigger /> )}
</div> >
<div className="overflow-auto h-[calc(100vh-3.5rem)] max-w-[1500px]"> <div className="flex items-center justify-between p-4">
{children} <h1
</div> className={cn(
</main> 'text-xl font-bold transition-all duration-300 ease-in-out',
</div> open ? 'block' : 'hidden',
</SidebarProvider> )}
>
Inventory
</h1>
<button
className="p-1.5 rounded-md hover:bg-gray-200"
onClick={() => setOpen(!open)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-5 h-5"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 6l16 0"></path>
<path d="M4 12l16 0"></path>
<path d="M4 18l12 0"></path>
</svg>
</button>
</div>
<nav className="p-4 space-y-2">
{links.map((link) => (
<NavLink
key={link.path}
to={link.path}
className={cn(
'flex items-center gap-2 p-2 rounded-md transition-all duration-300 ease-in-out',
location.pathname === link.path
? 'bg-gray-200'
: 'hover:bg-gray-100',
)}
>
{link.icon}
<span
className={cn(
'transition-all duration-300 ease-in-out',
open ? 'block' : 'hidden',
)}
>
{link.title}
</span>
</NavLink>
))}
<Button
variant="ghost"
className="w-full flex items-center gap-2"
onClick={handleLogout}
>
<LogOut />
<span
className={cn(
'transition-all duration-300 ease-in-out',
open ? 'block' : 'hidden',
)}
>
Logout
</span>
</Button>
</nav>
</aside>
<main className="flex-1 p-8 overflow-y-auto">{children}</main>
</div>
); );
} }

View File

@@ -2,7 +2,8 @@ const isDev = process.env.NODE_ENV === 'development';
const config = { const config = {
apiUrl: isDev ? '/api' : 'https://inventory.kent.pw/api', apiUrl: isDev ? '/api' : 'https://inventory.kent.pw/api',
baseUrl: isDev ? '' : 'https://inventory.kent.pw' baseUrl: isDev ? '' : 'https://inventory.kent.pw',
authUrl: isDev ? '/auth-inv' : 'https://inventory.kent.pw/auth-inv'
}; };
export default config; export default config;

View File

@@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import './index.css' import './index.css'
import App from './App.tsx' import App from './App.tsx'
import { BrowserRouter as Router } from 'react-router-dom'
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<App /> <Router>
<App />
</Router>
</StrictMode>, </StrictMode>,
) )

View File

@@ -0,0 +1,108 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { toast } from 'sonner';
import config from '../config';
const isDev = process.env.NODE_ENV === 'development';
export function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate();
const handleLogin = async () => {
try {
const url = isDev ? "/auth-inv/login" : `${config.authUrl}/login`;
console.log('Making login request:', {
url,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: { username, password },
config
});
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
credentials: 'include'
});
console.log('Login response status:', response.status);
if (!response.ok) {
const data = await response.json().catch(e => ({ error: 'Failed to parse error response' }));
console.error('Login failed:', data);
throw new Error(data.error || 'Login failed');
}
const data = await response.json();
console.log('Login successful:', data);
sessionStorage.setItem('token', data.token);
sessionStorage.setItem('isLoggedIn', 'true');
navigate('/');
} catch (error) {
console.error('Login error:', error);
toast.error(
error instanceof Error
? error.message
: 'An unexpected error occurred',
);
}
};
return (
<div className="flex items-center justify-center h-screen">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Login</CardTitle>
<CardDescription>Enter your credentials to access the inventory.</CardDescription>
</CardHeader>
<CardContent>
<form>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="username">Username</Label>
<Input
id="username"
placeholder="Enter your username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex justify-between">
<Button onClick={handleLogin}>Login</Button>
</CardFooter>
</Card>
</div>
);
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/dashboard/inventorystats.tsx","./src/components/dashboard/overview.tsx","./src/components/dashboard/recentsales.tsx","./src/components/dashboard/salesbycategory.tsx","./src/components/dashboard/trendingproducts.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/products/producteditdialog.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/hooks/use-mobile.tsx","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/dashboard.tsx","./src/pages/import.tsx","./src/pages/orders.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx"],"version":"5.6.3"} {"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/dashboard/inventorystats.tsx","./src/components/dashboard/overview.tsx","./src/components/dashboard/recentsales.tsx","./src/components/dashboard/salesbycategory.tsx","./src/components/dashboard/trendingproducts.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/products/producteditdialog.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/settings/calculationsettings.tsx","./src/components/settings/datamanagement.tsx","./src/components/settings/performancemetrics.tsx","./src/components/settings/stockmanagement.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/hooks/use-mobile.tsx","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/dashboard.tsx","./src/pages/import.tsx","./src/pages/login.tsx","./src/pages/orders.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx"],"version":"5.6.3"}

View File

@@ -1,141 +0,0 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
import path from "path";
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { loadEnv } from "vite";
import fs from 'fs-extra';
// https://vitejs.dev/config/
export default defineConfig(function (_a) {
var mode = _a.mode;
var env = loadEnv(mode, process.cwd(), "");
var isDev = mode === 'development';
return {
plugins: [
react(),
{
name: 'copy-build',
closeBundle: function () { return __awaiter(void 0, void 0, void 0, function () {
var sourcePath, targetPath, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!isDev) return [3 /*break*/, 6];
sourcePath = path.resolve(__dirname, 'build');
targetPath = path.resolve(__dirname, '../inventory-server/frontend/build');
_a.label = 1;
case 1:
_a.trys.push([1, 5, , 6]);
return [4 /*yield*/, fs.ensureDir(path.dirname(targetPath))];
case 2:
_a.sent();
return [4 /*yield*/, fs.remove(targetPath)];
case 3:
_a.sent();
return [4 /*yield*/, fs.copy(sourcePath, targetPath)];
case 4:
_a.sent();
console.log('Build files copied successfully to server directory!');
return [3 /*break*/, 6];
case 5:
error_1 = _a.sent();
console.error('Error copying build files:', error_1);
process.exit(1);
return [3 /*break*/, 6];
case 6: return [2 /*return*/];
}
});
}); }
}
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
host: "0.0.0.0",
port: 5173,
proxy: isDev ? {
"/api": {
target: "https://inventory.kent.pw",
changeOrigin: true,
secure: false,
ws: true,
xfwd: true,
cookieDomainRewrite: "",
withCredentials: true,
rewrite: function (path) { return path.replace(/^\/api/, "/api"); },
configure: function (proxy, _options) {
proxy.on("error", function (err, req, res) {
console.log("API proxy error:", err);
res.writeHead(500, {
"Content-Type": "application/json",
});
res.end(JSON.stringify({ error: "Proxy Error", message: err.message }));
});
proxy.on("proxyReq", function (proxyReq, req, _res) {
console.log("Outgoing request:", {
method: req.method,
url: req.url,
headers: proxyReq.getHeaders(),
});
});
proxy.on("proxyRes", function (proxyRes, req, _res) {
console.log("Proxy response:", {
statusCode: proxyRes.statusCode,
url: req.url,
headers: proxyRes.headers,
});
});
},
},
} : {},
},
build: {
outDir: "build",
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ["react", "react-dom", "react-router-dom"],
},
},
},
},
};
});

View File

@@ -32,6 +32,9 @@ export default defineConfig(({ mode }) => {
} }
} }
], ],
define: {
'process.env.NODE_ENV': JSON.stringify(mode)
},
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
@@ -40,7 +43,7 @@ export default defineConfig(({ mode }) => {
server: { server: {
host: "0.0.0.0", host: "0.0.0.0",
port: 5173, port: 5173,
proxy: isDev ? { proxy: {
"/api": { "/api": {
target: "https://inventory.kent.pw", target: "https://inventory.kent.pw",
changeOrigin: true, changeOrigin: true,
@@ -61,14 +64,14 @@ export default defineConfig(({ mode }) => {
) )
}) })
proxy.on("proxyReq", (proxyReq, req, _res) => { proxy.on("proxyReq", (proxyReq, req, _res) => {
console.log("Outgoing request:", { console.log("Outgoing request to API:", {
method: req.method, method: req.method,
url: req.url, url: req.url,
headers: proxyReq.getHeaders(), headers: proxyReq.getHeaders(),
}) })
}) })
proxy.on("proxyRes", (proxyRes, req, _res) => { proxy.on("proxyRes", (proxyRes, req, _res) => {
console.log("Proxy response:", { console.log("API Proxy response:", {
statusCode: proxyRes.statusCode, statusCode: proxyRes.statusCode,
url: req.url, url: req.url,
headers: proxyRes.headers, headers: proxyRes.headers,
@@ -76,7 +79,48 @@ export default defineConfig(({ mode }) => {
}) })
}, },
}, },
} : {}, "/auth-inv": {
target: "https://inventory.kent.pw",
changeOrigin: true,
secure: false,
ws: true,
xfwd: true,
cookieDomainRewrite: {
"inventory.kent.pw": "localhost"
},
withCredentials: true,
onProxyReq: (proxyReq, req) => {
// Add origin header to match CORS policy
proxyReq.setHeader('Origin', 'http://localhost:5173');
},
rewrite: (path) => path.replace(/^\/auth-inv/, "/auth-inv"),
configure: (proxy, _options) => {
proxy.on("error", (err, req, res) => {
console.log("Auth proxy error:", err)
res.writeHead(500, {
"Content-Type": "application/json",
})
res.end(
JSON.stringify({ error: "Proxy Error", message: err.message })
)
})
proxy.on("proxyReq", (proxyReq, req, _res) => {
console.log("Outgoing request to Auth:", {
method: req.method,
url: req.url,
headers: proxyReq.getHeaders(),
})
})
proxy.on("proxyRes", (proxyRes, req, _res) => {
console.log("Auth Proxy response:", {
statusCode: proxyRes.statusCode,
url: req.url,
headers: proxyRes.headers,
})
})
},
},
},
}, },
build: { build: {
outDir: "build", outDir: "build",