Changeset 591278c
- Timestamp:
- 02/22/26 17:24:14 (7 months ago)
- Branches:
- finki-main, main
- Children:
- 4dff800
- Parents:
- 69f2a41
- Files:
-
- 1 deleted
- 3 edited
-
database.js (modified) (43 diffs)
-
database/handcraft.db (deleted)
-
interfejs/store-owner.html (modified) (20 diffs)
-
server.js (modified) (60 diffs)
Legend:
- Unmodified
- Added
- Removed
-
database.js
r69f2a41 r591278c 24 24 database.run('PRAGMA foreign_keys = ON'); 25 25 26 // Helper function to ensure general category exists 26 // Helper function to ensure general category exists with ID 1 27 27 function ensureGeneralCategory(callback) { 28 database.get('SELECT category_id FROM category WHERE name = ?', ['General'], (err, row) => { 28 // First check if category with ID 1 exists and is named 'General' 29 database.get('SELECT category_id, name FROM category WHERE category_id = 1', [], (err, row) => { 29 30 if (err) { 30 31 callback(err); 31 } else if (!row) { 32 } else if (row && row.name === 'General') { 33 // Category with ID 1 already exists and is General 34 console.log('✅ General category exists with ID: 1'); 35 callback(null); 36 } else if (row && row.name !== 'General') { 37 // Category with ID 1 exists but has different name - update it 32 38 database.run( 33 ' INSERT INTO category (name, description) VALUES (?, ?)',39 'UPDATE category SET name = ?, description = ? WHERE category_id = 1', 34 40 ['General', 'General products category'], 35 41 function(err) { 36 callback(err); 42 if (err) { 43 callback(err); 44 } else { 45 console.log('✅ Updated category ID 1 to General'); 46 callback(null); 47 } 37 48 } 38 49 ); 39 50 } else { 40 callback(null); 51 // No category with ID 1 exists, create it 52 // First, check if we need to reset the autoincrement sequence 53 database.run( 54 'INSERT INTO category (category_id, name, description) VALUES (1, ?, ?)', 55 ['General', 'General products category'], 56 function(err) { 57 if (err) { 58 // If insert fails, try without specifying ID (let SQLite assign it) 59 database.run( 60 'INSERT INTO category (name, description) VALUES (?, ?)', 61 ['General', 'General products category'], 62 function(err) { 63 if (err) { 64 callback(err); 65 } else { 66 console.log('✅ Created General category with auto-assigned ID'); 67 callback(null); 68 } 69 } 70 ); 71 } else { 72 console.log('✅ Created General category with ID: 1'); 73 callback(null); 74 } 75 } 76 ); 41 77 } 42 78 }); … … 45 81 // Helper function to get the General category ID 46 82 function getGeneralCategoryId(callback) { 47 database.get('SELECT category_id FROM category WHERE name = ?', ['General'], (err, row) => { 83 // First try to get category with ID 1 that is named 'General' 84 database.get('SELECT category_id FROM category WHERE category_id = 1 AND name = ?', ['General'], (err, row) => { 48 85 if (err) { 49 86 callback(err, null); … … 51 88 callback(null, row.category_id); 52 89 } else { 53 // Create General category if it doesn't exist 54 database.run( 55 'INSERT INTO category (name, description) VALUES (?, ?)', 56 ['General', 'General products category'], 57 function(err) { 58 if (err) { 59 callback(err, null); 60 } else { 61 callback(null, this.lastID); 62 } 63 } 64 ); 90 // If not found with ID 1, try to find by name 91 database.get('SELECT category_id FROM category WHERE name = ?', ['General'], (err, row) => { 92 if (err) { 93 callback(err, null); 94 } else if (row) { 95 callback(null, row.category_id); 96 } else { 97 // Create General category if it doesn't exist 98 database.run( 99 'INSERT INTO category (name, description) VALUES (?, ?)', 100 ['General', 'General products category'], 101 function(err) { 102 if (err) { 103 callback(err, null); 104 } else { 105 const newId = this.lastID; 106 console.log(`✅ Created new General category with ID: ${newId}`); 107 callback(null, newId); 108 } 109 } 110 ); 111 } 112 }); 65 113 } 66 114 }); … … 84 132 database.all( 85 133 `SELECT r.* FROM roles r 86 JOIN user_roles ur ON r.role_id = ur.role_id87 WHERE ur.user_id = ?`,134 JOIN user_roles ur ON r.role_id = ur.role_id 135 WHERE ur.user_id = ?`, 88 136 [id], 89 137 (err, roles) => { … … 184 232 function getProducts(categoryId, searchTerm, callback) { 185 233 let query = ` 186 SELECT p.*, c.name as category_name, s.name as store_name187 FROM product p188 JOIN category c ON p.category_id = c.category_id189 JOIN store s ON p.store_id = s.store_id190 WHERE 1=1191 `;234 SELECT p.*, c.name as category_name, s.name as store_name 235 FROM product p 236 JOIN category c ON p.category_id = c.category_id 237 JOIN store s ON p.store_id = s.store_id 238 WHERE 1=1 239 `; 192 240 const params = []; 193 241 … … 214 262 database.get( 215 263 `SELECT p.*, c.name as category_name, s.name as store_name 216 FROM product p217 JOIN category c ON p.category_id = c.category_id218 JOIN store s ON p.store_id = s.store_id219 WHERE p.id = ?`,264 FROM product p 265 JOIN category c ON p.category_id = c.category_id 266 JOIN store s ON p.store_id = s.store_id 267 WHERE p.id = ?`, 220 268 [id], 221 269 (err, row) => { … … 256 304 database.get( 257 305 `SELECT p.*, c.name as category_name, s.name as store_name 258 FROM product p259 JOIN category c ON p.category_id = c.category_id260 JOIN store s ON p.store_id = s.store_id261 WHERE p.code = ?`,306 FROM product p 307 JOIN category c ON p.category_id = c.category_id 308 JOIN store s ON p.store_id = s.store_id 309 WHERE p.code = ?`, 262 310 [code], 263 311 (err, row) => { … … 317 365 database.run( 318 366 `INSERT INTO product ( 319 id, code, description, price, availability, weight, dimensions,320 production_time, category_id, store_id, created_at321 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,367 id, code, description, price, availability, weight, dimensions, 368 production_time, category_id, store_id, created_at 369 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`, 322 370 [ 323 371 'PROD_' + Date.now().toString().slice(-8), // Generate a unique ID … … 341 389 database.run( 342 390 `INSERT INTO "change" (date_and_time, product_code, changes) 343 VALUES (datetime('now'), ?, ?)`,391 VALUES (datetime('now'), ?, ?)`, 344 392 [productData.code, 'Product created'], 345 393 function(err) { … … 353 401 database.run( 354 402 `INSERT INTO makes_change (personal_id, change_date_time, product_code) 355 VALUES (?, datetime('now'), ?)`,403 VALUES (?, datetime('now'), ?)`, 356 404 [personalId, productData.code], 357 405 function(err) { … … 367 415 database.run( 368 416 `INSERT INTO image (product_code, image_url, is_primary) 369 VALUES (?, ?, ?)`,417 VALUES (?, ?, ?)`, 370 418 [productData.code, imageUrl, index === 0 ? 1 : 0], 371 419 function(err) { … … 409 457 params.push(productData.description); 410 458 } 459 411 460 if (productData.price !== undefined) { 412 461 updates.push('price = ?'); 413 462 params.push(productData.price); 414 463 } 464 415 465 if (productData.availability !== undefined) { 416 466 updates.push('availability = ?'); 417 467 params.push(productData.availability); 418 468 } 469 419 470 if (productData.weight !== undefined) { 420 471 updates.push('weight = ?'); 421 472 params.push(productData.weight); 422 473 } 474 423 475 if (productData.dimensions !== undefined) { 424 476 updates.push('dimensions = ?'); 425 477 params.push(productData.dimensions); 426 478 } 479 427 480 if (productData.production_time !== undefined) { 428 481 updates.push('production_time = ?'); 429 482 params.push(productData.production_time); 430 483 } 484 431 485 if (productData.category_id !== undefined) { 432 486 updates.push('category_id = ?'); … … 452 506 database.run( 453 507 `INSERT INTO "change" (date_and_time, product_code, changes) 454 VALUES (datetime('now'), ?, ?)`,508 VALUES (datetime('now'), ?, ?)`, 455 509 [productData.code, changesDesc], 456 510 function(err) { … … 461 515 database.run( 462 516 `INSERT INTO makes_change (personal_id, change_date_time, product_code) 463 VALUES (?, datetime('now'), ?)`,517 VALUES (?, datetime('now'), ?)`, 464 518 [personalId, productData.code], 465 519 function(err) { … … 520 574 database.run( 521 575 `INSERT INTO "change" (date_and_time, product_code, changes) 522 VALUES (datetime('now'), ?, ?)`,576 VALUES (datetime('now'), ?, ?)`, 523 577 [productCode, 'Product deleted'], 524 578 function(err) { … … 532 586 database.run( 533 587 `INSERT INTO makes_change (personal_id, change_date_time, product_code) 534 VALUES (?, datetime('now'), ?)`,588 VALUES (?, datetime('now'), ?)`, 535 589 [personalId, productCode], 536 590 function(err) { … … 571 625 database.all( 572 626 `SELECT c1.*, c2.name as parent_name 573 FROM category c1574 LEFT JOIN category c2 ON c1.parent_category_id = c2.category_id575 ORDER BY c1.name`,627 FROM category c1 628 LEFT JOIN category c2 ON c1.parent_category_id = c2.category_id 629 ORDER BY c1.name`, 576 630 [], 577 631 (err, rows) => { … … 610 664 database.all( 611 665 `SELECT p.*, c.name as category_name 612 FROM product p613 JOIN category c ON p.category_id = c.category_id614 WHERE p.store_id = ?615 ORDER BY p.code`,666 FROM product p 667 JOIN category c ON p.category_id = c.category_id 668 WHERE p.store_id = ? 669 ORDER BY p.code`, 616 670 [storeId], 617 671 (err, rows) => { … … 628 682 database.all( 629 683 `SELECT o.*, c.first_name, c.last_name 630 FROM "order" o631 JOIN client c ON o.client_id = c.client_id632 WHERE o.store_id = ?633 ORDER BY o.order_date DESC`,684 FROM "order" o 685 JOIN client c ON o.client_id = c.client_id 686 WHERE o.store_id = ? 687 ORDER BY o.order_date DESC`, 634 688 [storeId], 635 689 (err, rows) => { … … 649 703 database.all( 650 704 `SELECT oi.*, p.description 651 FROM order_items oi652 JOIN product p ON oi.product_code = p.code653 WHERE oi.order_num = ?`,705 FROM order_items oi 706 JOIN product p ON oi.product_code = p.code 707 WHERE oi.order_num = ?`, 654 708 [order.order_num], 655 709 (err, items) => { … … 659 713 order.items = []; 660 714 } 661 662 715 completed++; 663 716 if (completed === orders.length) { … … 675 728 database.all( 676 729 `SELECT p.*, e.date_of_hire, perm.type as permission_type, perm.authorisation 677 FROM personal p678 JOIN works_in_store w ON p.id = w.personal_id679 LEFT JOIN employees e ON p.id = e.employee_id680 LEFT JOIN permissions perm ON p.id = perm.personal_id681 WHERE w.store_id = ?`,730 FROM personal p 731 JOIN works_in_store w ON p.id = w.personal_id 732 LEFT JOIN employees e ON p.id = e.employee_id 733 LEFT JOIN permissions perm ON p.id = perm.personal_id 734 WHERE w.store_id = ?`, 682 735 [storeId], 683 736 (err, rows) => { … … 690 743 database.all( 691 744 `SELECT * FROM report 692 WHERE store_id = ?693 ORDER BY generated_at DESC`,745 WHERE store_id = ? 746 ORDER BY generated_at DESC`, 694 747 [storeId], 695 748 (err, rows) => { … … 719 772 database.get( 720 773 `SELECT SUM(oi.price * oi.quantity) as total_revenue 721 FROM order_items oi722 JOIN "order" o ON oi.order_num = o.order_num723 WHERE o.store_id = ?`,774 FROM order_items oi 775 JOIN "order" o ON oi.order_num = o.order_num 776 WHERE o.store_id = ?`, 724 777 [storeId], 725 778 (err, row) => { … … 729 782 database.get( 730 783 `SELECT AVG(rating) as avg_rating 731 FROM review r732 JOIN product p ON r.product_code = p.code733 WHERE p.store_id = ?`,784 FROM review r 785 JOIN product p ON r.product_code = p.code 786 WHERE p.store_id = ?`, 734 787 [storeId], 735 788 (err, row) => { 736 789 stats.avg_rating = row && row.avg_rating ? row.avg_rating : 0; 737 738 790 callback(null, stats); 739 791 } … … 757 809 database.run( 758 810 `INSERT INTO "order" (order_num, client_id, order_date, quantity, payment_method, 759 discount, delivery_address, store_id)760 VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?)`,811 discount, delivery_address, store_id) 812 VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?)`, 761 813 [ 762 814 orderData.order_num, … … 787 839 database.run( 788 840 `INSERT INTO order_items (order_num, product_code, quantity, price) 789 VALUES (?, ?, ?, ?)`,841 VALUES (?, ?, ?, ?)`, 790 842 [orderData.order_num, item.product_code, item.quantity, item.price], 791 843 function(err) { … … 817 869 database.all( 818 870 `SELECT o.*, s.name as store_name 819 FROM "order" o820 JOIN store s ON o.store_id = s.store_id821 WHERE o.client_id = ?822 ORDER BY o.order_date DESC`,871 FROM "order" o 872 JOIN store s ON o.store_id = s.store_id 873 WHERE o.client_id = ? 874 ORDER BY o.order_date DESC`, 823 875 [clientId], 824 876 (err, rows) => { … … 838 890 database.all( 839 891 `SELECT oi.*, p.description 840 FROM order_items oi841 JOIN product p ON oi.product_code = p.code842 WHERE oi.order_num = ?`,892 FROM order_items oi 893 JOIN product p ON oi.product_code = p.code 894 WHERE oi.order_num = ?`, 843 895 [order.order_num], 844 896 (err, items) => { … … 848 900 order.items = []; 849 901 } 850 851 902 completed++; 852 903 if (completed === orders.length) { … … 864 915 database.all( 865 916 `SELECT o.*, c.first_name, c.last_name, s.name as store_name 866 FROM "order" o867 JOIN client c ON o.client_id = c.client_id868 JOIN store s ON o.store_id = s.store_id869 ORDER BY o.order_date DESC`,917 FROM "order" o 918 JOIN client c ON o.client_id = c.client_id 919 JOIN store s ON o.store_id = s.store_id 920 ORDER BY o.order_date DESC`, 870 921 [], 871 922 (err, rows) => { … … 879 930 database.run( 880 931 `INSERT INTO review (review_id, client_id, product_code, rating, comment, review_date) 881 VALUES (?, ?, ?, ?, ?, datetime('now'))`,932 VALUES (?, ?, ?, ?, ?, datetime('now'))`, 882 933 [ 883 934 'REV' + Date.now().toString().slice(-8), … … 901 952 database.run( 902 953 `INSERT INTO request (request_num, date_and_time, problem, client_id, store_id) 903 VALUES (?, ?, ?, ?, ?)`,954 VALUES (?, ?, ?, ?, ?)`, 904 955 [ 905 956 requestData.request_num, … … 923 974 database.run( 924 975 `INSERT INTO refund (refund_id, order_num, amount, reason, request_date) 925 VALUES (?, ?, ?, ?, datetime('now'))`,976 VALUES (?, ?, ?, ?, datetime('now'))`, 926 977 [ 927 978 refundData.refund_id, … … 951 1002 database.all( 952 1003 `SELECT o.*, c.first_name, c.last_name 953 FROM "order" o954 JOIN client c ON o.client_id = c.client_id955 WHERE o.store_id = ? AND o.status = 'pending'956 ORDER BY o.order_date ASC`,1004 FROM "order" o 1005 JOIN client c ON o.client_id = c.client_id 1006 WHERE o.store_id = ? AND o.status = 'pending' 1007 ORDER BY o.order_date ASC`, 957 1008 [storeId], 958 1009 (err, rows) => { … … 964 1015 database.all( 965 1016 `SELECT r.*, c.first_name, c.last_name 966 FROM request r967 JOIN client c ON r.client_id = c.client_id968 WHERE r.store_id = ? AND r.status = 'pending'969 ORDER BY r.date_and_time ASC`,1017 FROM request r 1018 JOIN client c ON r.client_id = c.client_id 1019 WHERE r.store_id = ? AND r.status = 'pending' 1020 ORDER BY r.date_and_time ASC`, 970 1021 [storeId], 971 1022 (err, rows) => { … … 977 1028 database.all( 978 1029 `SELECT rf.*, o.client_id, c.first_name, c.last_name 979 FROM refund rf980 JOIN "order" o ON rf.order_num = o.order_num981 JOIN client c ON o.client_id = c.client_id982 WHERE o.store_id = ? AND rf.status = 'pending'983 ORDER BY rf.request_date ASC`,1030 FROM refund rf 1031 JOIN "order" o ON rf.order_num = o.order_num 1032 JOIN client c ON o.client_id = c.client_id 1033 WHERE o.store_id = ? AND rf.status = 'pending' 1034 ORDER BY rf.request_date ASC`, 984 1035 [storeId], 985 1036 (err, rows) => { … … 1010 1061 database.get( 1011 1062 `SELECT SUM(oi.price * oi.quantity) as total_spent 1012 FROM order_items oi1013 JOIN "order" o ON oi.order_num = o.order_num1014 WHERE o.client_id = ?`,1063 FROM order_items oi 1064 JOIN "order" o ON oi.order_num = o.order_num 1065 WHERE o.client_id = ?`, 1015 1066 [clientId], 1016 1067 (err, row) => { … … 1030 1081 (err, row) => { 1031 1082 stats.delivered_orders = row ? row.delivered_orders : 0; 1032 1033 1083 callback(null, stats); 1034 1084 } … … 1049 1099 database.all( 1050 1100 `SELECT client_id as id, first_name, last_name, email, 'client' as user_type 1051 FROM client1052 ORDER BY client_id`,1101 FROM client 1102 ORDER BY client_id`, 1053 1103 [], 1054 1104 (err, rows) => { … … 1060 1110 database.all( 1061 1111 `SELECT p.id, p.first_name, p.last_name, p.email, 1062 CASE WHEN b.boss_id IS NOT NULL THEN 'store_owner'1063 WHEN e.employee_id IS NOT NULL THEN 'store_employee'1064 ELSE 'personal' END as user_type1065 FROM personal p1066 LEFT JOIN boss b ON p.id = b.boss_id1067 LEFT JOIN employees e ON p.id = e.employee_id1068 ORDER BY p.id`,1112 CASE WHEN b.boss_id IS NOT NULL THEN 'store_owner' 1113 WHEN e.employee_id IS NOT NULL THEN 'store_employee' 1114 ELSE 'personal' END as user_type 1115 FROM personal p 1116 LEFT JOIN boss b ON p.id = b.boss_id 1117 LEFT JOIN employees e ON p.id = e.employee_id 1118 ORDER BY p.id`, 1069 1119 [], 1070 1120 (err, rows) => { … … 1076 1126 database.all( 1077 1127 `SELECT id, username, email, user_type 1078 FROM users1079 ORDER BY id`,1128 FROM users 1129 ORDER BY id`, 1080 1130 [], 1081 1131 (err, rows) => { … … 1096 1146 database.run( 1097 1147 `INSERT INTO audit_log (user_id, action, resource_type, resource_id, details, ip_address) 1098 VALUES (?, ?, ?, ?, ?, ?)`,1148 VALUES (?, ?, ?, ?, ?, ?)`, 1099 1149 [userId, action, resourceType, resourceId, details, ipAddress], 1100 1150 (err) => { -
interfejs/store-owner.html
r69f2a41 r591278c 151 151 </select> 152 152 </div> 153 153 154 <div id="custom-date-range" style="display: none;"> 154 155 <div class="form-row"> … … 163 164 </div> 164 165 </div> 166 165 167 <div class="form-group"> 166 168 <label for="report-type">Report Type</label> … … 172 174 </select> 173 175 </div> 176 174 177 <button id="generate-store-report" class="btn-primary">Generate Report</button> 178 175 179 <div id="report-results" style="margin-top: 2rem;"></div> 176 180 </div> … … 214 218 <input type="number" id="product-production-time" min="1" required> 215 219 </div> 216 <div class="form-group ">220 <div class="form-group category-group"> 217 221 <label for="product-category">Category *</label> 218 <select id="product-category" required> 219 <option value="">Select Category</option> 220 </select> 222 <div class="category-input-group"> 223 <select id="product-category" required> 224 <option value="">Select Category</option> 225 </select> 226 <button type="button" id="add-category-btn" class="btn-small btn-secondary">➕ New Category</button> 227 </div> 221 228 </div> 222 229 </div> … … 286 293 </main> 287 294 295 <!-- Edit Product Modal --> 288 296 <div id="edit-product-modal" class="modal" style="display: none;"> 289 297 <div class="modal-content modal-large"> … … 325 333 <input type="number" id="edit-product-production-time" min="1"> 326 334 </div> 327 <div class="form-group ">335 <div class="form-group category-group"> 328 336 <label for="edit-product-category">Category</label> 329 <select id="edit-product-category"> 330 <option value="">Select Category</option> 331 </select> 337 <div class="category-input-group"> 338 <select id="edit-product-category"> 339 <option value="">Select Category</option> 340 </select> 341 <button type="button" id="edit-add-category-btn" class="btn-small btn-secondary">➕ New Category</button> 342 </div> 332 343 </div> 333 344 </div> … … 348 359 </div> 349 360 361 <!-- Edit Employee Modal --> 350 362 <div id="edit-employee-modal" class="modal" style="display: none;"> 351 363 <div class="modal-content"> … … 381 393 382 394 <button type="submit" class="btn-primary">Update Employee</button> 395 </form> 396 </div> 397 </div> 398 399 <!-- Add Category Modal --> 400 <div id="add-category-modal" class="modal" style="display: none;"> 401 <div class="modal-content"> 402 <span class="close">×</span> 403 <h2>Create New Category</h2> 404 <form id="add-category-form" class="form"> 405 <div class="form-group"> 406 <label for="category-name">Category Name *</label> 407 <input type="text" id="category-name" required> 408 </div> 409 410 <div class="form-group"> 411 <label for="category-description">Description</label> 412 <textarea id="category-description" rows="3" placeholder="Optional description"></textarea> 413 </div> 414 415 <div class="form-group"> 416 <label for="category-parent">Parent Category (optional)</label> 417 <select id="category-parent"> 418 <option value="">None (Top Level Category)</option> 419 </select> 420 </div> 421 422 <button type="submit" class="btn-primary">Create Category</button> 423 <button type="button" id="cancel-category-btn" class="btn-secondary">Cancel</button> 383 424 </form> 384 425 </div> … … 408 449 document.addEventListener('DOMContentLoaded', async () => { 409 450 const user = await loadUserData(); 451 410 452 if (!user || user.userType !== 'store_owner') { 411 453 window.location.href = 'index.html'; … … 464 506 // Generate report 465 507 document.getElementById('generate-store-report').addEventListener('click', generateStoreReport); 508 509 // Add Category button functionality 510 document.getElementById('add-category-btn').addEventListener('click', () => { 511 openCategoryModal(); 512 }); 513 514 document.getElementById('edit-add-category-btn').addEventListener('click', () => { 515 openCategoryModal('edit'); 516 }); 517 518 // Category form submission 519 document.getElementById('add-category-form').addEventListener('submit', createCategory); 520 521 // Cancel button 522 document.getElementById('cancel-category-btn').addEventListener('click', () => { 523 document.getElementById('add-category-modal').style.display = 'none'; 524 }); 466 525 }); 467 526 … … 499 558 const response = await fetch(`/api/store-products?storeId=${storeId}`); 500 559 const data = await response.json(); 501 502 560 const tbody = document.getElementById('products-list'); 561 503 562 if (data.success && data.products.length > 0) { 504 563 tbody.innerHTML = data.products.map(product => ` … … 528 587 const response = await fetch(`/api/store-orders?storeId=${storeId}`); 529 588 const data = await response.json(); 530 531 589 const tbody = document.getElementById('orders-list'); 590 532 591 if (data.success && data.orders.length > 0) { 533 592 tbody.innerHTML = data.orders.map(order => { … … 560 619 const response = await fetch(`/api/store-employees?storeId=${storeId}`); 561 620 const data = await response.json(); 562 563 621 const tbody = document.getElementById('employees-list'); 622 564 623 if (data.success && data.employees.length > 0) { 565 624 tbody.innerHTML = data.employees.map(emp => ` … … 592 651 if (data.success) { 593 652 const options = data.categories.map(cat => 653 `<option value="${cat.category_id}">${cat.name}${cat.parent_name ? ` (subcategory of ${cat.parent_name})` : ''}</option>` 654 ).join(''); 655 656 document.getElementById('product-category').innerHTML = '<option value="">Select Category</option>' + options; 657 document.getElementById('edit-product-category').innerHTML = '<option value="">Select Category</option>' + options; 658 659 // Also load for category parent dropdown 660 const parentOptions = data.categories.map(cat => 594 661 `<option value="${cat.category_id}">${cat.name}</option>` 595 662 ).join(''); 596 597 document.getElementById('product-category').innerHTML = '<option value="">Select Category</option>' + options; 598 document.getElementById('edit-product-category').innerHTML = '<option value="">Select Category</option>' + options; 663 document.getElementById('category-parent').innerHTML = '<option value="">None (Top Level Category)</option>' + parentOptions; 599 664 } 600 665 } catch (error) { 601 666 console.error('Error loading categories:', error); 667 } 668 } 669 670 function openCategoryModal(source = 'add') { 671 // Store which form triggered this 672 document.getElementById('add-category-modal').dataset.source = source; 673 document.getElementById('add-category-modal').style.display = 'block'; 674 document.getElementById('add-category-form').reset(); 675 } 676 677 async function createCategory(e) { 678 e.preventDefault(); 679 680 const categoryName = document.getElementById('category-name').value.trim(); 681 const categoryDescription = document.getElementById('category-description').value.trim(); 682 const parentId = document.getElementById('category-parent').value; 683 684 if (!categoryName) { 685 alert('Please enter a category name'); 686 return; 687 } 688 689 const categoryData = { 690 name: categoryName, 691 description: categoryDescription, 692 parentId: parentId || null 693 }; 694 695 try { 696 const response = await fetch('/api/create-category', { 697 method: 'POST', 698 headers: { 'Content-Type': 'application/json' }, 699 body: JSON.stringify(categoryData) 700 }); 701 702 const data = await response.json(); 703 704 if (data.success) { 705 alert(`Category "${categoryName}" created successfully!`); 706 document.getElementById('add-category-modal').style.display = 'none'; 707 708 // Reload categories in all dropdowns 709 await loadCategories(); 710 711 // Auto-select the new category if it came from add product form 712 const source = document.getElementById('add-category-modal').dataset.source; 713 if (source === 'edit') { 714 document.getElementById('edit-product-category').value = data.category.id; 715 } else { 716 document.getElementById('product-category').value = data.category.id; 717 } 718 } else { 719 alert(data.message || 'Failed to create category'); 720 } 721 } catch (error) { 722 console.error('Error creating category:', error); 723 alert('An error occurred while creating the category'); 602 724 } 603 725 } … … 607 729 608 730 const storeId = document.getElementById('store-select').value; 731 609 732 if (!storeId) { 610 733 alert('Please select a store first'); … … 679 802 680 803 const storeId = document.getElementById('store-select').value; 804 681 805 if (!storeId) { 682 806 alert('Please select a store first'); … … 686 810 const password = document.getElementById('employee-password').value; 687 811 const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; 812 688 813 if (!passwordRegex.test(password)) { 689 814 alert('Password must have at least 8 characters, including uppercase, lowercase, number and special character'); … … 762 887 763 888 const storeId = document.getElementById('store-select').value; 889 764 890 const formData = { 765 891 code: document.getElementById('edit-product-code').value, … … 860 986 861 987 const storeId = document.getElementById('store-select').value; 988 862 989 const formData = { 863 990 employeeId: document.getElementById('edit-employee-id').value, … … 897 1024 898 1025 let startDate, endDate; 1026 899 1027 if (period === 'custom') { 900 1028 startDate = document.getElementById('report-start').value; 901 1029 endDate = document.getElementById('report-end').value; 1030 902 1031 if (!startDate || !endDate) { 903 1032 alert('Please select start and end dates'); -
server.js
r69f2a41 r591278c 10 10 11 11 const port = process.env.PORT || 3000; 12 12 13 const sessions = new Map(); 13 14 const verificationCodes = new Map(); … … 31 32 } 32 33 }; 34 33 35 emailTransporter = nodemailer.createTransport(emailConfig); 34 36 … … 73 75 subject: 'Your Verification Code - Handcraft Marketplace', 74 76 html: ` 75 <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #4169E1 0%, #FF69B4 100%); color: white; padding: 20px; border-radius: 10px;">76 <h2 style="text-align: center;">🎨 Handcraft Marketplace</h2>77 <div style="background-color: white; color: #333; padding: 20px; border-radius: 8px; margin: 20px 0;">78 <h3 style="color: #4169E1;">Account Verification</h3>79 <p>Your verification code is:</p>80 <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 5px; margin: 20px 0; color: #4169E1;">81 ${code}82 </div>83 <p style="color: #e74c3c; font-weight: bold;">⚠️ This code will expire in 30 seconds</p>84 <p style="color: #666; font-size: 12px;">If you didn't request this verification, please ignore this email.</p>85 </div>86 </div>`77 <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #4169E1 0%, #FF69B4 100%); color: white; padding: 20px; border-radius: 10px;"> 78 <h2 style="text-align: center;">🎨 Handcraft Marketplace</h2> 79 <div style="background-color: white; color: #333; padding: 20px; border-radius: 8px; margin: 20px 0;"> 80 <h3 style="color: #4169E1;">Account Verification</h3> 81 <p>Your verification code is:</p> 82 <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 5px; margin: 20px 0; color: #4169E1;"> 83 ${code} 84 </div> 85 <p style="color: #e74c3c; font-weight: bold;">⚠️ This code will expire in 30 seconds</p> 86 <p style="color: #666; font-size: 12px;">If you didn't request this verification, please ignore this email.</p> 87 </div> 88 </div>` 87 89 }; 88 90 … … 104 106 subject: 'Your 2FA Code - Handcraft Marketplace', 105 107 html: ` 106 <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #4169E1 0%, #FF69B4 100%); color: white; padding: 20px; border-radius: 10px;">107 <h2 style="text-align: center;">🎨 Handcraft Marketplace</h2>108 <div style="background-color: white; color: #333; padding: 20px; border-radius: 8px; margin: 20px 0;">109 <h3 style="color: #4169E1;">Two-Factor Authentication</h3>110 <p>Your login verification code is:</p>111 <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 5px; margin: 20px 0; color: #4169E1;">112 ${code}113 </div>114 <p style="color: #e74c3c; font-weight: bold;">⚠️ This code will expire in 30 seconds</p>115 <p style="color: #666; font-size: 12px;">If you're not trying to login, please secure your account immediately.</p>116 </div>117 </div>`108 <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #4169E1 0%, #FF69B4 100%); color: white; padding: 20px; border-radius: 10px;"> 109 <h2 style="text-align: center;">🎨 Handcraft Marketplace</h2> 110 <div style="background-color: white; color: #333; padding: 20px; border-radius: 8px; margin: 20px 0;"> 111 <h3 style="color: #4169E1;">Two-Factor Authentication</h3> 112 <p>Your login verification code is:</p> 113 <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 5px; margin: 20px 0; color: #4169E1;"> 114 ${code} 115 </div> 116 <p style="color: #e74c3c; font-weight: bold;">⚠️ This code will expire in 30 seconds</p> 117 <p style="color: #666; font-size: 12px;">If you're not trying to login, please secure your account immediately.</p> 118 </div> 119 </div>` 118 120 }; 119 121 … … 135 137 subject: 'Store Registration Verification - Handcraft Marketplace', 136 138 html: ` 137 <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #4169E1 0%, #FF69B4 100%); color: white; padding: 20px; border-radius: 10px;">138 <h2 style="text-align: center;">🎨 Handcraft Marketplace</h2>139 <div style="background-color: white; color: #333; padding: 20px; border-radius: 8px; margin: 20px 0;">140 <h3 style="color: #4169E1;">Store Registration Verification</h3>141 <p>Thank you for registering your store "<strong>${storeName}</strong>" on Handcraft Marketplace!</p>142 <p>Your verification code is:</p>143 <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 5px; margin: 20px 0; color: #4169E1;">144 ${code}145 </div>146 <p style="color: #e74c3c; font-weight: bold;">⚠️ This code will expire in 30 seconds</p>147 <p style="color: #666; font-size: 12px;">If you didn't request this verification, please ignore this email.</p>148 </div>149 </div>`139 <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #4169E1 0%, #FF69B4 100%); color: white; padding: 20px; border-radius: 10px;"> 140 <h2 style="text-align: center;">🎨 Handcraft Marketplace</h2> 141 <div style="background-color: white; color: #333; padding: 20px; border-radius: 8px; margin: 20px 0;"> 142 <h3 style="color: #4169E1;">Store Registration Verification</h3> 143 <p>Thank you for registering your store "<strong>${storeName}</strong>" on Handcraft Marketplace!</p> 144 <p>Your verification code is:</p> 145 <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 5px; margin: 20px 0; color: #4169E1;"> 146 ${code} 147 </div> 148 <p style="color: #e74c3c; font-weight: bold;">⚠️ This code will expire in 30 seconds</p> 149 <p style="color: #666; font-size: 12px;">If you didn't request this verification, please ignore this email.</p> 150 </div> 151 </div>` 150 152 }; 151 153 … … 350 352 351 353 try { 352 // Check if all tables exist 353 const checkTablesQuery = ` 354 SELECT table_name 355 FROM information_schema.tables 356 WHERE table_schema = 'public' 357 `; 358 354 // For SQLite, we need to use a different approach to check tables 359 355 const result = await new Promise((resolve, reject) => { 360 database.database.all(checkTablesQuery, [], (err, rows) => { 361 if (err) reject(err); 362 else resolve(rows || []); 363 }); 364 }); 365 366 const existingTables = result.map(row => row.table_name); 356 database.database.all( 357 "SELECT name FROM sqlite_master WHERE type='table'", 358 [], 359 (err, rows) => { 360 if (err) reject(err); 361 else resolve(rows || []); 362 } 363 ); 364 }); 365 366 const existingTables = result.map(row => row.name); 367 367 const missingTables = requiredTables.filter(table => !existingTables.includes(table)); 368 368 … … 409 409 // Drop in reverse order of creation (respect foreign keys) 410 410 const dropQueries = [ 411 'DROP TABLE IF EXISTS user_roles CASCADE',412 'DROP TABLE IF EXISTS roles CASCADE',413 'DROP TABLE IF EXISTS delivery_address CASCADE',414 'DROP TABLE IF EXISTS image CASCADE',415 'DROP TABLE IF EXISTS color CASCADE',416 'DROP TABLE IF EXISTS audit_log CASCADE',417 'DROP TABLE IF EXISTS report CASCADE',418 'DROP TABLE IF EXISTS refund CASCADE',419 'DROP TABLE IF EXISTS request CASCADE',420 'DROP TABLE IF EXISTS review CASCADE',421 'DROP TABLE IF EXISTS order_items CASCADE',422 'DROP TABLE IF EXISTS "order" CASCADE',423 'DROP TABLE IF EXISTS permissions CASCADE',424 'DROP TABLE IF EXISTS works_in_store CASCADE',425 'DROP TABLE IF EXISTS employees CASCADE',426 'DROP TABLE IF EXISTS boss CASCADE',427 'DROP TABLE IF EXISTS product CASCADE',428 'DROP TABLE IF EXISTS personal CASCADE',429 'DROP TABLE IF EXISTS users CASCADE',430 'DROP TABLE IF EXISTS category CASCADE',431 'DROP TABLE IF EXISTS store CASCADE',432 'DROP TABLE IF EXISTS client CASCADE'411 'DROP TABLE IF EXISTS user_roles', 412 'DROP TABLE IF EXISTS roles', 413 'DROP TABLE IF EXISTS delivery_address', 414 'DROP TABLE IF EXISTS image', 415 'DROP TABLE IF EXISTS color', 416 'DROP TABLE IF EXISTS audit_log', 417 'DROP TABLE IF EXISTS report', 418 'DROP TABLE IF EXISTS refund', 419 'DROP TABLE IF EXISTS request', 420 'DROP TABLE IF EXISTS review', 421 'DROP TABLE IF EXISTS order_items', 422 'DROP TABLE IF EXISTS "order"', 423 'DROP TABLE IF EXISTS permissions', 424 'DROP TABLE IF EXISTS works_in_store', 425 'DROP TABLE IF EXISTS employees', 426 'DROP TABLE IF EXISTS boss', 427 'DROP TABLE IF EXISTS product', 428 'DROP TABLE IF EXISTS personal', 429 'DROP TABLE IF EXISTS users', 430 'DROP TABLE IF EXISTS category', 431 'DROP TABLE IF EXISTS store', 432 'DROP TABLE IF EXISTS client' 433 433 ]; 434 434 … … 463 463 // Client table (SERIAL ID starting from 1000) 464 464 `CREATE TABLE IF NOT EXISTS client ( 465 client_id SERIAL PRIMARY KEY,466 first_name VARCHAR(100) NOT NULL,467 last_name VARCHAR(100) NOT NULL,468 email VARCHAR(255) UNIQUE NOT NULL,469 password VARCHAR(255) NOT NULL,470 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP471 )`,465 client_id INTEGER PRIMARY KEY AUTOINCREMENT, 466 first_name VARCHAR(100) NOT NULL, 467 last_name VARCHAR(100) NOT NULL, 468 email VARCHAR(255) UNIQUE NOT NULL, 469 password VARCHAR(255) NOT NULL, 470 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 471 )`, 472 472 473 473 // Store table (VARCHAR ID) 474 474 `CREATE TABLE IF NOT EXISTS store ( 475 store_id VARCHAR(10) PRIMARY KEY,476 name VARCHAR(255) NOT NULL,477 date_of_founding DATE NOT NULL,478 physical_address TEXT NOT NULL,479 store_email VARCHAR(255) UNIQUE NOT NULL,480 rating DECIMAL(3,2) DEFAULT 0.0481 )`,475 store_id VARCHAR(10) PRIMARY KEY, 476 name VARCHAR(255) NOT NULL, 477 date_of_founding DATE NOT NULL, 478 physical_address TEXT NOT NULL, 479 store_email VARCHAR(255) UNIQUE NOT NULL, 480 rating DECIMAL(3,2) DEFAULT 0.0 481 )`, 482 482 483 483 // Category table (SERIAL ID starting from 1) 484 484 `CREATE TABLE IF NOT EXISTS category ( 485 category_id SERIAL PRIMARY KEY,486 name VARCHAR(100) NOT NULL,487 description TEXT,488 parent_category_id INTEGER REFERENCES category(category_id) ON DELETE SET NULL489 )`,485 category_id INTEGER PRIMARY KEY AUTOINCREMENT, 486 name VARCHAR(100) NOT NULL, 487 description TEXT, 488 parent_category_id INTEGER REFERENCES category(category_id) ON DELETE SET NULL 489 )`, 490 490 491 491 // Users table (VARCHAR ID) 492 492 `CREATE TABLE IF NOT EXISTS users ( 493 id VARCHAR(50) PRIMARY KEY,494 username VARCHAR(100) UNIQUE NOT NULL,495 email VARCHAR(255) UNIQUE NOT NULL,496 password VARCHAR(255) NOT NULL,497 user_type VARCHAR(50) NOT NULL,498 force_password_change INTEGER DEFAULT 0,499 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP500 )`,493 id VARCHAR(50) PRIMARY KEY, 494 username VARCHAR(100) UNIQUE NOT NULL, 495 email VARCHAR(255) UNIQUE NOT NULL, 496 password VARCHAR(255) NOT NULL, 497 user_type VARCHAR(50) NOT NULL, 498 force_password_change INTEGER DEFAULT 0, 499 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 500 )`, 501 501 502 502 // Personal table (VARCHAR ID - format: storeId(3) + '001' for owner, storeId(3) + employeeNum(3) for employees) 503 503 `CREATE TABLE IF NOT EXISTS personal ( 504 id VARCHAR(10) PRIMARY KEY,505 first_name VARCHAR(100) NOT NULL,506 last_name VARCHAR(100) NOT NULL,507 ssn VARCHAR(13) UNIQUE NOT NULL,508 email VARCHAR(255) UNIQUE NOT NULL,509 password VARCHAR(255) NOT NULL,510 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP511 )`,504 id VARCHAR(10) PRIMARY KEY, 505 first_name VARCHAR(100) NOT NULL, 506 last_name VARCHAR(100) NOT NULL, 507 ssn VARCHAR(13) UNIQUE NOT NULL, 508 email VARCHAR(255) UNIQUE NOT NULL, 509 password VARCHAR(255) NOT NULL, 510 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 511 )`, 512 512 513 513 // Product table (VARCHAR ID) 514 514 `CREATE TABLE IF NOT EXISTS product ( 515 id VARCHAR(50) PRIMARY KEY,516 code VARCHAR(20) UNIQUE NOT NULL,517 description TEXT NOT NULL,518 price DECIMAL(10,2) NOT NULL,519 availability INTEGER NOT NULL DEFAULT 0,520 weight DECIMAL(10,2),521 dimensions VARCHAR(50),522 production_time INTEGER,523 category_id INTEGER REFERENCES category(category_id) ON DELETE SET NULL,524 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,525 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP526 )`,515 id VARCHAR(50) PRIMARY KEY, 516 code VARCHAR(20) UNIQUE NOT NULL, 517 description TEXT NOT NULL, 518 price DECIMAL(10,2) NOT NULL, 519 availability INTEGER NOT NULL DEFAULT 0, 520 weight DECIMAL(10,2), 521 dimensions VARCHAR(50), 522 production_time INTEGER, 523 category_id INTEGER REFERENCES category(category_id) ON DELETE SET NULL, 524 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE, 525 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 526 )`, 527 527 528 528 // Boss table (VARCHAR ID - references personal.id) 529 529 `CREATE TABLE IF NOT EXISTS boss ( 530 boss_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE,531 signature TEXT NOT NULL,532 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP533 )`,530 boss_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE, 531 signature TEXT NOT NULL, 532 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 533 )`, 534 534 535 535 // Employees table (VARCHAR ID - references personal.id) 536 536 `CREATE TABLE IF NOT EXISTS employees ( 537 employee_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE,538 date_of_hire DATE NOT NULL,539 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP540 )`,537 employee_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE, 538 date_of_hire DATE NOT NULL, 539 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 540 )`, 541 541 542 542 // Works_in_store table (junction) 543 543 `CREATE TABLE IF NOT EXISTS works_in_store ( 544 personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE,545 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,546 PRIMARY KEY (personal_id, store_id)547 )`,544 personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE, 545 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE, 546 PRIMARY KEY (personal_id, store_id) 547 )`, 548 548 549 549 // Permissions table 550 550 `CREATE TABLE IF NOT EXISTS permissions ( 551 permission_id SERIAL PRIMARY KEY,552 personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE,553 type VARCHAR(50) NOT NULL,554 authorisation TEXT,555 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP556 )`,551 permission_id INTEGER PRIMARY KEY AUTOINCREMENT, 552 personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE, 553 type VARCHAR(50) NOT NULL, 554 authorisation TEXT, 555 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 556 )`, 557 557 558 558 // Order table (VARCHAR ID) 559 559 `CREATE TABLE IF NOT EXISTS "order" ( 560 order_num VARCHAR(20) PRIMARY KEY,561 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL,562 order_date TIMESTAMP NOT NULL,563 quantity INTEGER NOT NULL,564 payment_method VARCHAR(50) NOT NULL,565 discount DECIMAL(10,2) DEFAULT 0,566 delivery_address TEXT NOT NULL,567 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE SET NULL,568 status VARCHAR(50) DEFAULT 'pending',569 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP570 )`,560 order_num VARCHAR(20) PRIMARY KEY, 561 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL, 562 order_date TIMESTAMP NOT NULL, 563 quantity INTEGER NOT NULL, 564 payment_method VARCHAR(50) NOT NULL, 565 discount DECIMAL(10,2) DEFAULT 0, 566 delivery_address TEXT NOT NULL, 567 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE SET NULL, 568 status VARCHAR(50) DEFAULT 'pending', 569 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 570 )`, 571 571 572 572 // Order_items table 573 573 `CREATE TABLE IF NOT EXISTS order_items ( 574 item_id SERIAL PRIMARY KEY,575 order_num VARCHAR(20) REFERENCES "order"(order_num) ON DELETE CASCADE,576 product_code VARCHAR(20) REFERENCES product(code) ON DELETE SET NULL,577 quantity INTEGER NOT NULL,578 price DECIMAL(10,2) NOT NULL,579 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP580 )`,574 item_id INTEGER PRIMARY KEY AUTOINCREMENT, 575 order_num VARCHAR(20) REFERENCES "order"(order_num) ON DELETE CASCADE, 576 product_code VARCHAR(20) REFERENCES product(code) ON DELETE SET NULL, 577 quantity INTEGER NOT NULL, 578 price DECIMAL(10,2) NOT NULL, 579 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 580 )`, 581 581 582 582 // Review table (VARCHAR ID) 583 583 `CREATE TABLE IF NOT EXISTS review ( 584 review_id VARCHAR(20) PRIMARY KEY,585 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL,586 product_code VARCHAR(20) REFERENCES product(code) ON DELETE CASCADE,587 rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),588 comment TEXT,589 review_date TIMESTAMP NOT NULL,590 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP591 )`,584 review_id VARCHAR(20) PRIMARY KEY, 585 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL, 586 product_code VARCHAR(20) REFERENCES product(code) ON DELETE CASCADE, 587 rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5), 588 comment TEXT, 589 review_date TIMESTAMP NOT NULL, 590 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 591 )`, 592 592 593 593 // Request table (VARCHAR ID) 594 594 `CREATE TABLE IF NOT EXISTS request ( 595 request_num VARCHAR(50) PRIMARY KEY,596 date_and_time TIMESTAMP NOT NULL,597 problem TEXT NOT NULL,598 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL,599 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,600 status VARCHAR(50) DEFAULT 'pending',601 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP602 )`,595 request_num VARCHAR(50) PRIMARY KEY, 596 date_and_time TIMESTAMP NOT NULL, 597 problem TEXT NOT NULL, 598 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL, 599 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE, 600 status VARCHAR(50) DEFAULT 'pending', 601 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 602 )`, 603 603 604 604 // Refund table (VARCHAR ID) 605 605 `CREATE TABLE IF NOT EXISTS refund ( 606 refund_id VARCHAR(50) PRIMARY KEY,607 order_num VARCHAR(20) REFERENCES "order"(order_num) ON DELETE CASCADE,608 amount DECIMAL(10,2) NOT NULL,609 reason TEXT NOT NULL,610 status VARCHAR(50) DEFAULT 'pending',611 request_date TIMESTAMP NOT NULL,612 processed_date TIMESTAMP,613 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP614 )`,606 refund_id VARCHAR(50) PRIMARY KEY, 607 order_num VARCHAR(20) REFERENCES "order"(order_num) ON DELETE CASCADE, 608 amount DECIMAL(10,2) NOT NULL, 609 reason TEXT NOT NULL, 610 status VARCHAR(50) DEFAULT 'pending', 611 request_date TIMESTAMP NOT NULL, 612 processed_date TIMESTAMP, 613 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 614 )`, 615 615 616 616 // Report table (VARCHAR ID) 617 617 `CREATE TABLE IF NOT EXISTS report ( 618 id VARCHAR(50) PRIMARY KEY,619 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,620 period VARCHAR(50) NOT NULL,621 start_date DATE NOT NULL,622 end_date DATE NOT NULL,623 type VARCHAR(50) NOT NULL,624 generated_by VARCHAR(10) REFERENCES personal(id) ON DELETE SET NULL,625 generated_at TIMESTAMP NOT NULL,626 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP627 )`,618 id VARCHAR(50) PRIMARY KEY, 619 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE, 620 period VARCHAR(50) NOT NULL, 621 start_date DATE NOT NULL, 622 end_date DATE NOT NULL, 623 type VARCHAR(50) NOT NULL, 624 generated_by VARCHAR(10) REFERENCES personal(id) ON DELETE SET NULL, 625 generated_at TIMESTAMP NOT NULL, 626 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 627 )`, 628 628 629 629 // Audit_log table (SERIAL ID) 630 630 `CREATE TABLE IF NOT EXISTS audit_log ( 631 log_id SERIAL PRIMARY KEY,632 user_id VARCHAR(50),633 action VARCHAR(100) NOT NULL,634 resource_type VARCHAR(50),635 resource_id VARCHAR(50),636 details TEXT,637 ip_address VARCHAR(45),638 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP639 )`,631 log_id INTEGER PRIMARY KEY AUTOINCREMENT, 632 user_id VARCHAR(50), 633 action VARCHAR(100) NOT NULL, 634 resource_type VARCHAR(50), 635 resource_id VARCHAR(50), 636 details TEXT, 637 ip_address VARCHAR(45), 638 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 639 )`, 640 640 641 641 // Color table (SERIAL ID) 642 642 `CREATE TABLE IF NOT EXISTS color ( 643 color_id SERIAL PRIMARY KEY,644 name VARCHAR(50) NOT NULL,645 hex_code VARCHAR(7) NOT NULL,646 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP647 )`,643 color_id INTEGER PRIMARY KEY AUTOINCREMENT, 644 name VARCHAR(50) NOT NULL, 645 hex_code VARCHAR(7) NOT NULL, 646 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 647 )`, 648 648 649 649 // Image table (SERIAL ID) 650 650 `CREATE TABLE IF NOT EXISTS image ( 651 image_id SERIAL PRIMARY KEY,652 product_code VARCHAR(20) REFERENCES product(code) ON DELETE CASCADE,653 image_url TEXT NOT NULL,654 is_primary BOOLEAN DEFAULT FALSE,655 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP656 )`,651 image_id INTEGER PRIMARY KEY AUTOINCREMENT, 652 product_code VARCHAR(20) REFERENCES product(code) ON DELETE CASCADE, 653 image_url TEXT NOT NULL, 654 is_primary BOOLEAN DEFAULT FALSE, 655 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 656 )`, 657 657 658 658 // Delivery_address table (SERIAL ID) 659 659 `CREATE TABLE IF NOT EXISTS delivery_address ( 660 address_id SERIAL PRIMARY KEY,661 client_id INTEGER REFERENCES client(client_id) ON DELETE CASCADE,662 address TEXT NOT NULL,663 city VARCHAR(100) NOT NULL,664 postcode VARCHAR(20) NOT NULL,665 country VARCHAR(100) NOT NULL,666 is_default BOOLEAN DEFAULT FALSE,667 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP668 )`,660 address_id INTEGER PRIMARY KEY AUTOINCREMENT, 661 client_id INTEGER REFERENCES client(client_id) ON DELETE CASCADE, 662 address TEXT NOT NULL, 663 city VARCHAR(100) NOT NULL, 664 postcode VARCHAR(20) NOT NULL, 665 country VARCHAR(100) NOT NULL, 666 is_default BOOLEAN DEFAULT FALSE, 667 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 668 )`, 669 669 670 670 // Roles table (SERIAL ID) 671 671 `CREATE TABLE IF NOT EXISTS roles ( 672 role_id SERIAL PRIMARY KEY,673 name VARCHAR(50) UNIQUE NOT NULL,674 description TEXT,675 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP676 )`,672 role_id INTEGER PRIMARY KEY AUTOINCREMENT, 673 name VARCHAR(50) UNIQUE NOT NULL, 674 description TEXT, 675 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 676 )`, 677 677 678 678 // User_roles table (junction) 679 679 `CREATE TABLE IF NOT EXISTS user_roles ( 680 user_id VARCHAR(50) REFERENCES users(id) ON DELETE CASCADE,681 role_id INTEGER REFERENCES roles(role_id) ON DELETE CASCADE,682 PRIMARY KEY (user_id, role_id)683 )`680 user_id VARCHAR(50) REFERENCES users(id) ON DELETE CASCADE, 681 role_id INTEGER REFERENCES roles(role_id) ON DELETE CASCADE, 682 PRIMARY KEY (user_id, role_id) 683 )` 684 684 ]; 685 685 … … 766 766 console.log('📝 Inserting initial data...'); 767 767 768 // Insert General category (ID will be 1 due to SERIAL) 769 database.database.run( 770 `INSERT INTO category (name, description) 771 VALUES ('General', 'General products category') 772 ON CONFLICT DO NOTHING`, 773 [], 774 (err) => { 775 if (err) { 776 console.error('Error inserting General category:', err.message); 777 } 778 } 779 ); 768 // REMOVED: Category insertion - now handled by database.ensureGeneralCategory() 780 769 781 770 // Insert admin user … … 785 774 database.database.run( 786 775 `INSERT INTO users (id, username, email, password, user_type, force_password_change) 787 VALUES ($1, $2, $3, $4, $5, $6)788 ON CONFLICT DO NOTHING`,776 VALUES ($1, $2, $3, $4, $5, $6) 777 ON CONFLICT DO NOTHING`, 789 778 [adminId, 'admin', 'admin@handcraft.com', adminPassword, 'admin', 1], 790 779 (err) => { … … 811 800 database.database.run( 812 801 `INSERT INTO roles (name, description) 813 VALUES ($1, $2)814 ON CONFLICT DO NOTHING`,802 VALUES ($1, $2) 803 ON CONFLICT DO NOTHING`, 815 804 [role.name, role.description], 816 805 (err) => { … … 822 811 console.log('✅ Roles inserted'); 823 812 824 // Check ifGeneral category exists813 // Ensure General category exists 825 814 database.ensureGeneralCategory((err) => { 826 815 if (err) { 827 816 console.error('Error ensuring General category:', err.message); 828 817 } else { 829 console.log('✅ General category exists');818 console.log('✅ General category checked/created'); 830 819 } 831 820 resolve(); … … 925 914 body += chunk.toString(); 926 915 }); 916 927 917 req.on('end', () => { 928 918 const { username, email, password, userType, firstName, lastName } = JSON.parse(body); … … 989 979 console.log('✅ Verification email sent to:', email); 990 980 database.logAudit(null, 'REGISTER_ATTEMPT', 'user', null, `Registration attempt for ${email} as ${userType}`, ipAddress); 981 991 982 res.writeHead(200, { 'Content-Type': 'application/json' }); 992 983 res.end(JSON.stringify({ … … 1016 1007 body += chunk.toString(); 1017 1008 }); 1009 1018 1010 req.on('end', () => { 1019 1011 const formData = JSON.parse(body); … … 1182 1174 console.log('✅ Store registration email sent to:', formData.ownerEmail); 1183 1175 database.logAudit(null, 'STORE_REGISTER_ATTEMPT', 'store', null, `Store registration attempt: ${formData.storeName}`, ipAddress); 1176 1184 1177 res.writeHead(200, { 'Content-Type': 'application/json' }); 1185 1178 res.end(JSON.stringify({ … … 1214 1207 body += chunk.toString(); 1215 1208 }); 1209 1216 1210 req.on('end', () => { 1217 1211 const { firstName, lastName, email, password, address, city, postcode, country, isDefaultAddress } = JSON.parse(body); … … 1278 1272 console.log('✅ Verification email sent to:', email); 1279 1273 database.logAudit(null, 'CLIENT_REGISTER_ATTEMPT', 'client', null, `Client registration attempt for ${email}`, ipAddress); 1274 1280 1275 res.writeHead(200, { 'Content-Type': 'application/json' }); 1281 1276 res.end(JSON.stringify({ … … 1304 1299 body += chunk.toString(); 1305 1300 }); 1301 1306 1302 req.on('end', () => { 1307 1303 const { email } = JSON.parse(body); … … 1438 1434 body += chunk.toString(); 1439 1435 }); 1436 1440 1437 req.on('end', () => { 1441 1438 const { email, code } = JSON.parse(body); … … 1668 1665 } else { 1669 1666 const userId = 'user_' + Date.now().toString().slice(-8); 1667 1670 1668 database.createUser(userId, tempUserData.username, tempUserData.email, tempUserData.password, tempUserData.userType, (err, userId) => { 1671 1669 if (err) { … … 1698 1696 body += chunk.toString(); 1699 1697 }); 1698 1700 1699 req.on('end', () => { 1701 1700 const { email, password } = JSON.parse(body); 1701 1702 1702 console.log(`🔍 Login attempt for email: ${email}`); 1703 1703 … … 1710 1710 if (client) { 1711 1711 console.log(`🔍 Found client: ${client.email}`); 1712 1712 1713 if (!client.password) { 1713 1714 console.log('❌ Client has no password set'); … … 1732 1733 const sessionId = generateSessionId(); 1733 1734 const clientId = client.client_ID; 1735 1734 1736 sessions.set(sessionId, `client_${clientId}`); 1735 1737 1736 1738 console.log(`✅ Client login successful. Session: ${sessionId}, User: client_${clientId}`); 1739 1737 1740 database.logAudit(clientId, 'LOGIN_SUCCESS', 'auth', 1738 1741 typeof clientId === 'string' ? clientId : String(clientId), … … 1743 1746 'Set-Cookie': `sessionId=${sessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=Strict` 1744 1747 }); 1745 1746 1748 res.end(JSON.stringify({ 1747 1749 success: true, … … 1768 1770 if (personal) { 1769 1771 console.log(`🔍 Found personal user: ${personal.email}`); 1772 1770 1773 if (!personal.password) { 1771 1774 console.log('❌ Personal has no password set'); … … 1803 1806 1804 1807 const twoFACode = generateVerificationCode(); 1808 1805 1809 verificationCodes.set(personal.email, { 1806 1810 code: twoFACode, … … 1862 1866 1863 1867 const twoFACode = generateVerificationCode(); 1868 1864 1869 verificationCodes.set(personal.email, { 1865 1870 code: twoFACode, … … 1923 1928 1924 1929 const twoFACode = generateVerificationCode(); 1930 1925 1931 verificationCodes.set(userByUsername.email, { 1926 1932 code: twoFACode, … … 1973 1979 1974 1980 const twoFACode = generateVerificationCode(); 1981 1975 1982 verificationCodes.set(user.email, { 1976 1983 code: twoFACode, … … 2038 2045 body += chunk.toString(); 2039 2046 }); 2047 2040 2048 req.on('end', () => { 2041 2049 const { email } = JSON.parse(body); … … 2060 2068 2061 2069 const newTwoFACode = generateVerificationCode(); 2070 2062 2071 verificationCodes.set(userByUsername.email, { 2063 2072 code: newTwoFACode, … … 2095 2104 2096 2105 const newTwoFACode = generateVerificationCode(); 2106 2097 2107 verificationCodes.set(user.email, { 2098 2108 code: newTwoFACode, … … 2135 2145 body += chunk.toString(); 2136 2146 }); 2147 2137 2148 req.on('end', () => { 2138 2149 const { email, code } = JSON.parse(body); … … 2173 2184 'Set-Cookie': `sessionId=${tempSessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=Strict` 2174 2185 }); 2175 2176 2186 res.end(JSON.stringify({ 2177 2187 success: true, … … 2203 2213 // Determine redirect based on user type 2204 2214 let redirectTo = ''; 2215 2205 2216 switch(verificationData.userType) { 2206 2217 case 'client': … … 2226 2237 'Set-Cookie': `sessionId=${sessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=Strict` 2227 2238 }); 2228 2229 2239 res.end(JSON.stringify({ 2230 2240 success: true, … … 2279 2289 if (userIdStr.startsWith('client_')) { 2280 2290 const clientId = parseInt(userIdStr.replace('client_', '')); 2291 2281 2292 database.getClientById(clientId, (err, client) => { 2282 2293 if (err || !client) { … … 2298 2309 }); 2299 2310 } 2311 2300 2312 else if (userIdStr.startsWith('personal_')) { 2301 2313 const personalId = userIdStr.replace('personal_', ''); 2314 2302 2315 database.getPersonalById(personalId, (err, personal) => { 2303 2316 if (err || !personal) { … … 2318 2331 database.database.all( 2319 2332 `SELECT s.* FROM store s 2320 JOIN works_in_store w ON s.store_id = w.store_id2321 WHERE w.personal_id = $1`,2333 JOIN works_in_store w ON s.store_id = w.store_id 2334 WHERE w.personal_id = $1`, 2322 2335 [personalId], 2323 2336 (err, stores) => { … … 2353 2366 database.database.all( 2354 2367 `SELECT s.* FROM store s 2355 JOIN works_in_store w ON s.store_id = w.store_id2356 WHERE w.personal_id = $1`,2368 JOIN works_in_store w ON s.store_id = w.store_id 2369 WHERE w.personal_id = $1`, 2357 2370 [personalId], 2358 2371 (err, stores) => { … … 2445 2458 body += chunk.toString(); 2446 2459 }); 2460 2447 2461 req.on('end', () => { 2448 2462 const categoryData = JSON.parse(body); … … 2470 2484 } else { 2471 2485 database.logAudit(personalId, 'CATEGORY_CREATED', 'category', category.id.toString(), `New category created: ${category.name}`, ipAddress); 2486 2472 2487 res.writeHead(200, { 'Content-Type': 'application/json' }); 2473 2488 res.end(JSON.stringify({ … … 2526 2541 body += chunk.toString(); 2527 2542 }); 2543 2528 2544 req.on('end', () => { 2529 2545 const orderData = JSON.parse(body); 2530 2531 2546 const userIdStr = String(userId); 2532 2547 … … 2601 2616 if (userIdStr.startsWith('client_')) { 2602 2617 const clientId = parseInt(userIdStr.replace('client_', '')); 2618 2603 2619 database.getOrdersByClient(clientId, (err, orders) => { 2604 2620 if (err) { … … 2623 2639 body += chunk.toString(); 2624 2640 }); 2641 2625 2642 req.on('end', () => { 2626 2643 const reviewData = JSON.parse(body); 2627 2628 2644 const userIdStr = String(userId); 2629 2645 2630 2646 if (userIdStr.startsWith('client_')) { 2631 2647 const clientId = parseInt(userIdStr.replace('client_', '')); 2648 2632 2649 reviewData.client_id = clientId; 2633 2650 … … 2656 2673 body += chunk.toString(); 2657 2674 }); 2675 2658 2676 req.on('end', () => { 2659 2677 const requestData = JSON.parse(body); 2660 2661 2678 const userIdStr = String(userId); 2662 2679 … … 2726 2743 body += chunk.toString(); 2727 2744 }); 2745 2728 2746 req.on('end', () => { 2729 2747 const refundData = JSON.parse(body); 2730 2731 2748 const userIdStr = String(userId); 2732 2749 … … 2745 2762 2746 2763 const storeId = result[0].store_id; 2747 2748 2764 const now = new Date(); 2749 2765 const month = (now.getMonth() + 1).toString().padStart(2, '0'); … … 2797 2813 body += chunk.toString(); 2798 2814 }); 2815 2799 2816 req.on('end', () => { 2800 2817 const productData = JSON.parse(body); … … 2828 2845 } 2829 2846 2847 // FIXED: Changed SQL syntax from SUBSTRING(code FROM 4) to SUBSTR(code, 4) for SQLite compatibility 2830 2848 database.database.get( 2831 'SELECT MAX(CAST(SUBSTR ING(code FROM4) AS INTEGER)) as max_product_num FROM product WHERE store_id = $1',2849 'SELECT MAX(CAST(SUBSTR(code, 4) AS INTEGER)) as max_product_num FROM product WHERE store_id = $1', 2832 2850 [storeId], 2833 2851 (err, result) => { … … 2863 2881 } else { 2864 2882 database.logAudit(personalId, 'PRODUCT_ADDED', 'product', productId.toString(), 'New product added', ipAddress); 2883 2865 2884 res.writeHead(200, { 'Content-Type': 'application/json' }); 2866 2885 res.end(JSON.stringify({ … … 2888 2907 body += chunk.toString(); 2889 2908 }); 2909 2890 2910 req.on('end', () => { 2891 2911 const productData = JSON.parse(body); … … 2996 3016 body += chunk.toString(); 2997 3017 }); 3018 2998 3019 req.on('end', () => { 2999 3020 const { currentPassword, newPassword, confirmPassword } = JSON.parse(body); … … 3089 3110 body += chunk.toString(); 3090 3111 }); 3112 3091 3113 req.on('end', () => { 3092 3114 const { firstName, lastName, ssn, email, password, storeId, dateOfHire } = JSON.parse(body); … … 3300 3322 body += chunk.toString(); 3301 3323 }); 3324 3302 3325 req.on('end', () => { 3303 3326 const { employeeId, storeId } = JSON.parse(body); … … 3451 3474 body += chunk.toString(); 3452 3475 }); 3476 3453 3477 req.on('end', () => { 3454 3478 const { employeeId, storeId, status } = JSON.parse(body); … … 3550 3574 body += chunk.toString(); 3551 3575 }); 3576 3552 3577 req.on('end', () => { 3553 3578 const { employeeId, storeId, firstName, lastName, email } = JSON.parse(body); … … 3966 3991 body += chunk.toString(); 3967 3992 }); 3993 3968 3994 req.on('end', () => { 3969 3995 const { productCode, storeId } = JSON.parse(body); … … 4035 4061 body += chunk.toString(); 4036 4062 }); 4063 4037 4064 req.on('end', () => { 4038 4065 const { storeId, period, startDate, endDate, type } = JSON.parse(body);
Note:
See TracChangeset
for help on using the changeset viewer.
