first commit
This commit is contained in:
commit
81a1448ed3
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "scanramproducts",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^5.2.1",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,604 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Product Management</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
padding: 25px 30px;
|
||||
background: #f8f9fa;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: #2c3e50;
|
||||
box-shadow: 0 0 0 3px rgba(44, 62, 80, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(44, 62, 80, 0.4);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
padding: 0 30px 30px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
th:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
th.sorted::after {
|
||||
content: ' ▼';
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
th.sorted.asc::after {
|
||||
content: ' ▲';
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f1f3f5;
|
||||
color: #495057;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background: #c82333;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 8px 16px;
|
||||
border: 2px solid #e9ecef;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-btn:hover:not(:disabled) {
|
||||
border-color: #2c3e50;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-btn.active {
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
|
||||
color: white;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
color: #6c757d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
.empty h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.controls {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px 8px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #495057;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Product Management</h1>
|
||||
<p>Manage your products efficiently</p>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchInput" placeholder="Search by ID, MPN, SN, or creator...">
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="openCreateModal()">
|
||||
Add Product
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
Loading data...
|
||||
</div>
|
||||
|
||||
<div id="empty" class="empty" style="display: none;">
|
||||
<h3>No products found</h3>
|
||||
<p>Add your first product to get started!</p>
|
||||
</div>
|
||||
|
||||
<table id="productTable" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th onclick="sortTable('id')">ID</th>
|
||||
<th onclick="sortTable('mpn')">MPN</th>
|
||||
<th onclick="sortTable('sn')">SN</th>
|
||||
<th onclick="sortTable('createdAt')">Created At</th>
|
||||
<th onclick="sortTable('createdBy')">Created By</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="productBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination" id="pagination"></div>
|
||||
</div>
|
||||
|
||||
<!-- Create Product Modal -->
|
||||
<div id="createModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Add New Product</h2>
|
||||
<div class="form-group">
|
||||
<label>MPN *</label>
|
||||
<input type="text" id="mpnInput" placeholder="Enter MPN code">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Serial Number (SN) *</label>
|
||||
<input type="text" id="snInput" placeholder="Enter serial number">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Created By *</label>
|
||||
<input type="text" id="createdByInput" placeholder="Enter creator name">
|
||||
</div>
|
||||
<div class="modal-buttons">
|
||||
<button class="btn btn-secondary" onclick="closeCreateModal()">Cancel</button>
|
||||
<button class="btn btn-primary" onclick="createProduct()">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentSort = 'id';
|
||||
let currentOrder = 'DESC';
|
||||
let searchTerm = '';
|
||||
let URL = "https://logs1.danielvu.com/mobile-api/"
|
||||
async function loadProducts() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('productTable').style.display = 'none';
|
||||
document.getElementById('empty').style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${URL}product/get?page=${currentPage}&sortBy=${currentSort}&sortOrder=${currentOrder}&search=${encodeURIComponent(searchTerm)}`);
|
||||
const data = await response.json();
|
||||
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
|
||||
if (data.products.length === 0) {
|
||||
document.getElementById('empty').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('productTable').style.display = 'table';
|
||||
renderProducts(data.products);
|
||||
renderPagination(data);
|
||||
}
|
||||
|
||||
updateSortIndicators();
|
||||
} catch (error) {
|
||||
console.error('Error loading products:', error);
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function renderProducts(products) {
|
||||
const tbody = document.getElementById('productBody');
|
||||
tbody.innerHTML = products.map(product => {
|
||||
// Convert UTC to client's local timezone
|
||||
const date = new Date(product.createdAt+"Z");
|
||||
const formattedDate = date.toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${product.id}</td>
|
||||
<td>${product.mpn}</td>
|
||||
<td>${product.sn}</td>
|
||||
<td>${formattedDate}</td>
|
||||
<td>${product.createdBy}</td>
|
||||
<td>
|
||||
<button class="delete-btn" onclick="deleteProduct(${product.id})">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
let html = '';
|
||||
|
||||
html += `<button class="page-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})">Previous</button>`;
|
||||
|
||||
const startPage = Math.max(1, currentPage - 2);
|
||||
const endPage = Math.min(data.totalPages, currentPage + 2);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
html += `<button class="page-btn ${i === currentPage ? 'active' : ''}" onclick="changePage(${i})">${i}</button>`;
|
||||
}
|
||||
|
||||
html += `<button class="page-btn" ${currentPage === data.totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})">Next</button>`;
|
||||
html += `<span class="page-info">Total: ${data.total} products</span>`;
|
||||
|
||||
pagination.innerHTML = html;
|
||||
}
|
||||
|
||||
function changePage(page) {
|
||||
currentPage = page;
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
function sortTable(column) {
|
||||
if (currentSort === column) {
|
||||
currentOrder = currentOrder === 'DESC' ? 'ASC' : 'DESC';
|
||||
} else {
|
||||
currentSort = column;
|
||||
currentOrder = 'DESC';
|
||||
}
|
||||
currentPage = 1;
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
function updateSortIndicators() {
|
||||
document.querySelectorAll('th').forEach(th => {
|
||||
th.classList.remove('sorted', 'asc');
|
||||
});
|
||||
|
||||
const columns = ['id', 'mpn', 'sn', 'createdAt', 'createdBy'];
|
||||
const index = columns.indexOf(currentSort);
|
||||
if (index !== -1) {
|
||||
const th = document.querySelectorAll('th')[index];
|
||||
th.classList.add('sorted');
|
||||
if (currentOrder === 'ASC') {
|
||||
th.classList.add('asc');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let searchTimeout;
|
||||
document.getElementById('searchInput').addEventListener('input', (e) => {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
searchTerm = e.target.value;
|
||||
currentPage = 1;
|
||||
loadProducts();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
async function deleteProduct(id) {
|
||||
if (!confirm('Are you sure you want to delete this product?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${URL}product/delete/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
loadProducts();
|
||||
} else {
|
||||
alert('Error deleting product');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting product:', error);
|
||||
alert('Error deleting product');
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateModal() {
|
||||
document.getElementById('createModal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeCreateModal() {
|
||||
document.getElementById('createModal').classList.remove('active');
|
||||
document.getElementById('mpnInput').value = '';
|
||||
document.getElementById('snInput').value = '';
|
||||
document.getElementById('createdByInput').value = '';
|
||||
}
|
||||
|
||||
async function createProduct() {
|
||||
const mpn = document.getElementById('mpnInput').value.trim();
|
||||
const sn = document.getElementById('snInput').value.trim();
|
||||
const createdBy = document.getElementById('createdByInput').value.trim();
|
||||
|
||||
if (!mpn || !sn || !createdBy) {
|
||||
alert('Please fill in all fields');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(URL+'product/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify([{ mpn, sn, createdBy }])
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
closeCreateModal();
|
||||
loadProducts();
|
||||
} else {
|
||||
alert('Error creating product');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating product:', error);
|
||||
alert('Error creating product');
|
||||
}
|
||||
}
|
||||
|
||||
// Load products on page load
|
||||
loadProducts();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
const express = require('express');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Initialize SQLite database
|
||||
const db = new sqlite3.Database('./products.db', (err) => {
|
||||
if (err) console.error(err.message);
|
||||
console.log('Connected to products database.');
|
||||
});
|
||||
|
||||
// Create products table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS products (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mpn TEXT NOT NULL,
|
||||
sn TEXT NOT NULL,
|
||||
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
createdBy TEXT NOT NULL
|
||||
)`);
|
||||
|
||||
// API Routes
|
||||
|
||||
// Create products (batch insert)
|
||||
app.post('/api/product/create', (req, res) => {
|
||||
const products = req.body;
|
||||
|
||||
if (!Array.isArray(products) || products.length === 0) {
|
||||
return res.status(400).json({ error: 'Expected an array of products' });
|
||||
}
|
||||
|
||||
const stmt = db.prepare('INSERT INTO products (mpn, sn, createdBy) VALUES (?, ?, ?)');
|
||||
let inserted = 0;
|
||||
let errors = [];
|
||||
|
||||
products.forEach((product, index) => {
|
||||
if (!product.mpn || !product.sn || !product.createdBy) {
|
||||
errors.push(`Product at index ${index} is missing required fields`);
|
||||
return;
|
||||
}
|
||||
|
||||
stmt.run(product.mpn, product.sn, product.createdBy, (err) => {
|
||||
if (err) errors.push(`Error inserting product at index ${index}: ${err.message}`);
|
||||
else inserted++;
|
||||
});
|
||||
});
|
||||
|
||||
stmt.finalize(() => {
|
||||
res.json({
|
||||
success: true,
|
||||
inserted,
|
||||
errors: errors.length > 0 ? errors : undefined
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Get products with pagination, search, and sort
|
||||
app.get('/api/product/get', (req, res) => {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 50;
|
||||
const search = req.query.search || '';
|
||||
const sortBy = req.query.sortBy || 'id';
|
||||
const sortOrder = req.query.sortOrder || 'DESC';
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Validate sortBy to prevent SQL injection
|
||||
const validColumns = ['id', 'mpn', 'sn', 'createdAt', 'createdBy'];
|
||||
const column = validColumns.includes(sortBy) ? sortBy : 'id';
|
||||
const order = sortOrder.toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
|
||||
|
||||
let query = 'SELECT * FROM products';
|
||||
let countQuery = 'SELECT COUNT(*) as total FROM products';
|
||||
let params = [];
|
||||
|
||||
if (search) {
|
||||
const searchCondition = ` WHERE mpn LIKE ? OR sn LIKE ? OR createdBy LIKE ? OR id LIKE ?`;
|
||||
query += searchCondition;
|
||||
countQuery += searchCondition;
|
||||
const searchParam = `%${search}%`;
|
||||
params = [searchParam, searchParam, searchParam, searchParam];
|
||||
}
|
||||
|
||||
query += ` ORDER BY ${column} ${order} LIMIT ? OFFSET ?`;
|
||||
params.push(limit, offset);
|
||||
|
||||
db.get(countQuery, search ? params.slice(0, 4) : [], (err, countRow) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
||||
db.all(query, params, (err, rows) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
||||
res.json({
|
||||
products: rows,
|
||||
total: countRow.total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(countRow.total / limit)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Delete product
|
||||
app.delete('/api/product/delete/:id', (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
db.run('DELETE FROM products WHERE id = ?', id, function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
||||
if (this.changes === 0) {
|
||||
return res.status(404).json({ error: 'Product not found' });
|
||||
}
|
||||
|
||||
res.json({ success: true, deleted: this.changes });
|
||||
});
|
||||
});
|
||||
|
||||
// Serve HTML UI
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 4444;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
Loading…
Reference in New Issue