source: server.js@ 591278c

finki-main main
Last change on this file since 591278c was 591278c, checked in by Klimentina Efremova <klimentina08642@โ€ฆ>, 7 months ago

Fixed Product and Category adding

  • Property mode set to 100644
File size: 206.0 KB
Lineย 
1const http = require('http');
2const url = require('url');
3const database = require('./database.js');
4const fs = require('fs');
5const path = require('path');
6const crypto = require('crypto');
7const nodemailer = require('nodemailer');
8const bcrypt = require('bcryptjs');
9require('dotenv').config();
10
11const port = process.env.PORT || 3000;
12
13const sessions = new Map();
14const verificationCodes = new Map();
15const tempUsers = new Map();
16const tempAdminSessions = new Map();
17const tempStoreRegistrations = new Map();
18
19console.log('๐Ÿ”ง Starting Handcraft Marketplace Server...');
20console.log('๐ŸŽจ Colors: Royal Blue & Pink Theme');
21
22let emailTransporter;
23
24if (process.env.SMTP_USER && process.env.SMTP_PASS) {
25 const emailConfig = {
26 host: process.env.SMTP_HOST || 'smtp.gmail.com',
27 port: parseInt(process.env.SMTP_PORT) || 587,
28 secure: false,
29 auth: {
30 user: process.env.SMTP_USER,
31 pass: process.env.SMTP_PASS
32 }
33 };
34
35 emailTransporter = nodemailer.createTransport(emailConfig);
36
37 emailTransporter.verify(function(error, success) {
38 if (error) {
39 console.log('โŒ Email configuration failed:', error.message);
40 console.log('๐Ÿ“ง Falling back to console display for verification codes');
41 emailTransporter = createMockTransporter();
42 } else {
43 console.log('โœ… Email server is ready to send real emails!');
44 }
45 });
46} else {
47 console.log('๐Ÿ“ง No email credentials found. Verification codes will be shown in console.');
48 emailTransporter = createMockTransporter();
49}
50
51function createMockTransporter() {
52 return {
53 sendMail: function(mailOptions) {
54 return new Promise((resolve, reject) => {
55 const codeMatch = mailOptions.html.match(/\b\d{6}\b/);
56 const code = codeMatch ? codeMatch[0] : 'unknown';
57 console.log('');
58 console.log('๐ŸŽฏ ===== VERIFICATION CODE =====');
59 console.log('๐Ÿ“ง For:', mailOptions.to);
60 console.log('๐Ÿ” CODE:', code);
61 console.log('โฐ Expires in: 30 seconds');
62 console.log('๐Ÿ“ Use this code to continue');
63 console.log('================================');
64 console.log('');
65 resolve({ messageId: 'dev-' + Date.now() });
66 });
67 }
68 };
69}
70
71function sendVerificationEmail(toEmail, code) {
72 const mailOptions = {
73 from: process.env.SMTP_USER || 'noreply@handcraft-marketplace.com',
74 to: toEmail,
75 subject: 'Your Verification Code - Handcraft Marketplace',
76 html: `
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>`
89 };
90
91 console.log('');
92 console.log('๐ŸŽฏ ===== VERIFICATION CODE FOR TESTING =====');
93 console.log('๐Ÿ“ง Email:', toEmail);
94 console.log('๐Ÿ” CODE:', code);
95 console.log('โฐ Expires in: 30 seconds');
96 console.log('==========================================');
97 console.log('');
98
99 return emailTransporter.sendMail(mailOptions);
100}
101
102function send2FACode(toEmail, code) {
103 const mailOptions = {
104 from: process.env.SMTP_USER || 'noreply@handcraft-marketplace.com',
105 to: toEmail,
106 subject: 'Your 2FA Code - Handcraft Marketplace',
107 html: `
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>`
120 };
121
122 console.log('');
123 console.log('๐ŸŽฏ ===== 2FA CODE FOR TESTING =====');
124 console.log('๐Ÿ“ง Email:', toEmail);
125 console.log('๐Ÿ” CODE:', code);
126 console.log('โฐ Expires in: 30 seconds');
127 console.log('==================================');
128 console.log('');
129
130 return emailTransporter.sendMail(mailOptions);
131}
132
133function sendStoreRegistrationEmail(toEmail, code, storeName) {
134 const mailOptions = {
135 from: process.env.SMTP_USER || 'noreply@handcraft-marketplace.com',
136 to: toEmail,
137 subject: 'Store Registration Verification - Handcraft Marketplace',
138 html: `
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>`
152 };
153
154 console.log('');
155 console.log('๐ŸŽฏ ===== STORE REGISTRATION VERIFICATION CODE =====');
156 console.log('๐Ÿ“ง For:', toEmail);
157 console.log('๐Ÿช Store:', storeName);
158 console.log('๐Ÿ” CODE:', code);
159 console.log('โฐ Expires in: 30 seconds');
160 console.log('==================================================');
161 console.log('');
162
163 return emailTransporter.sendMail(mailOptions);
164}
165
166function generateVerificationCode() {
167 let code = '';
168 for(let i = 0; i < 6; i++) {
169 code += crypto.randomInt(0, 9);
170 }
171 return code;
172}
173
174function generateSessionId() {
175 return crypto.randomBytes(32).toString('hex');
176}
177
178function serveStaticFile(res, filePath, contentType) {
179 const fullPath = path.join(__dirname, 'interfejs', filePath);
180 fs.readFile(fullPath, (err, data) => {
181 if (err) {
182 console.error('File not found:', fullPath, err);
183 res.writeHead(404, { 'Content-Type': 'text/plain' });
184 res.end('File not found');
185 } else {
186 res.writeHead(200, { 'Content-Type': contentType });
187 res.end(data);
188 }
189 });
190}
191
192function parseCookies(req) {
193 const cookieHeader = req.headers.cookie;
194 const cookies = {};
195 if (cookieHeader) {
196 cookieHeader.split(';').forEach(cookie => {
197 const parts = cookie.split('=');
198 cookies[parts[0].trim()] = parts[1]?.trim();
199 });
200 }
201 return cookies;
202}
203
204function getClientIp(req) {
205 return req.headers['x-forwarded-for'] ||
206 req.connection.remoteAddress ||
207 req.socket.remoteAddress ||
208 (req.connection.socket ? req.connection.socket.remoteAddress : null);
209}
210
211function requireAuth(req, res, callback) {
212 const cookies = parseCookies(req);
213 const sessionId = cookies.sessionId;
214
215 if (!sessionId || !sessions.has(sessionId)) {
216 res.writeHead(302, { 'Location': '/login.html' });
217 res.end();
218 return;
219 }
220
221 const userId = sessions.get(sessionId);
222
223 if (tempAdminSessions.has(sessionId)) {
224 if (!req.url.includes('/change-password') && !req.url.includes('/api/force-change-password')) {
225 res.writeHead(302, { 'Location': '/change-password.html?forced=true' });
226 res.end();
227 return;
228 }
229 }
230
231 callback(userId);
232}
233
234function requireRole(roleName) {
235 return function(req, res, callback) {
236 requireAuth(req, res, (userId) => {
237 database.getUserById(userId, (err, user) => {
238 if (err || !user) {
239 res.writeHead(403, { 'Content-Type': 'application/json' });
240 res.end(JSON.stringify({ success: false, message: 'Access denied' }));
241 return;
242 }
243
244 const hasRole = user.roles && user.roles.some(role => role.name === roleName);
245
246 if (!hasRole) {
247 res.writeHead(403, { 'Content-Type': 'application/json' });
248 res.end(JSON.stringify({ success: false, message: 'Insufficient permissions' }));
249 return;
250 }
251
252 callback(userId, user);
253 });
254 });
255 };
256}
257
258function validateEmail(email) {
259 const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
260 return emailRegex.test(email);
261}
262
263function validatePassword(password) {
264 const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
265 return passwordRegex.test(password);
266}
267
268function cleanupExpiredCodes() {
269 const now = Date.now();
270 let cleanedCount = 0;
271
272 for (const [key, data] of verificationCodes.entries()) {
273 if (now - data.timestamp > 30 * 1000) {
274 verificationCodes.delete(key);
275 cleanedCount++;
276 }
277 }
278
279 for (const [key, data] of tempUsers.entries()) {
280 if (now - data.timestamp > 30 * 1000) {
281 tempUsers.delete(key);
282 cleanedCount++;
283 }
284 }
285
286 for (const [key, data] of tempStoreRegistrations.entries()) {
287 if (now - data.timestamp > 30 * 1000) {
288 tempStoreRegistrations.delete(key);
289 cleanedCount++;
290 }
291 }
292
293 if (cleanedCount > 0) {
294 console.log(`๐Ÿงน Cleaned ${cleanedCount} expired verification codes`);
295 }
296}
297
298setInterval(cleanupExpiredCodes, 10 * 1000);
299
300function requireStoreOwner() {
301 return function(req, res, callback) {
302 requireAuth(req, res, (userId) => {
303 const userIdStr = String(userId);
304 const personalId = userIdStr.replace('personal_', '');
305
306 database.database.get(
307 'SELECT boss_id FROM boss WHERE boss_id = $1',
308 [personalId],
309 (err, boss) => {
310 if (err || !boss) {
311 res.writeHead(403, { 'Content-Type': 'application/json' });
312 res.end(JSON.stringify({ success: false, message: 'Access denied - not a store owner' }));
313 return;
314 }
315
316 callback(personalId);
317 }
318 );
319 });
320 };
321}
322
323// Database initialization function
324async function initializeDatabase() {
325 console.log('๐Ÿ” Checking database schema...');
326
327 // List of all required tables
328 const requiredTables = [
329 'client',
330 'store',
331 'category',
332 'users',
333 'personal',
334 'product',
335 'boss',
336 'employees',
337 'works_in_store',
338 'permissions',
339 'order',
340 'order_items',
341 'review',
342 'request',
343 'refund',
344 'report',
345 'audit_log',
346 'color',
347 'image',
348 'delivery_address',
349 'roles',
350 'user_roles'
351 ];
352
353 try {
354 // For SQLite, we need to use a different approach to check tables
355 const result = await new Promise((resolve, reject) => {
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 const missingTables = requiredTables.filter(table => !existingTables.includes(table));
368
369 if (missingTables.length > 0) {
370 console.log(`โš ๏ธ Missing tables: ${missingTables.join(', ')}`);
371 console.log('๐Ÿ”„ Recreating entire database...');
372
373 // Drop all tables in correct order (respecting foreign keys)
374 await dropAllTables();
375
376 // Create all tables
377 await createAllTables();
378
379 // Create indexes
380 await createIndexes();
381
382 // Insert initial data
383 await insertInitialData();
384
385 console.log('โœ… Database recreation completed');
386 } else {
387 console.log('โœ… All required tables exist');
388 }
389 } catch (err) {
390 console.error('โŒ Error checking database schema:', err);
391 console.log('โš ๏ธ Attempting to recreate database anyway...');
392
393 try {
394 await dropAllTables();
395 await createAllTables();
396 await createIndexes();
397 await insertInitialData();
398 console.log('โœ… Database recreation completed');
399 } catch (createErr) {
400 console.error('โŒ Failed to recreate database:', createErr);
401 }
402 }
403}
404
405function dropAllTables() {
406 return new Promise((resolve, reject) => {
407 console.log('๐Ÿ—‘๏ธ Dropping all tables...');
408
409 // Drop in reverse order of creation (respect foreign keys)
410 const dropQueries = [
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 ];
434
435 let index = 0;
436
437 function runNext() {
438 if (index >= dropQueries.length) {
439 console.log('โœ… All tables dropped');
440 resolve();
441 return;
442 }
443
444 database.database.run(dropQueries[index], [], (err) => {
445 if (err) {
446 console.error(`Error dropping table: ${err.message}`);
447 // Continue anyway
448 }
449 index++;
450 runNext();
451 });
452 }
453
454 runNext();
455 });
456}
457
458function createAllTables() {
459 return new Promise((resolve, reject) => {
460 console.log('๐Ÿ—๏ธ Creating tables...');
461
462 const createQueries = [
463 // Client table (SERIAL ID starting from 1000)
464 `CREATE TABLE IF NOT EXISTS client (
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
473 // Store table (VARCHAR ID)
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.0
481 )`,
482
483 // Category table (SERIAL ID starting from 1)
484 `CREATE TABLE IF NOT EXISTS category (
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
491 // Users table (VARCHAR ID)
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_TIMESTAMP
500 )`,
501
502 // Personal table (VARCHAR ID - format: storeId(3) + '001' for owner, storeId(3) + employeeNum(3) for employees)
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_TIMESTAMP
511 )`,
512
513 // Product table (VARCHAR ID)
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_TIMESTAMP
526 )`,
527
528 // Boss table (VARCHAR ID - references personal.id)
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_TIMESTAMP
533 )`,
534
535 // Employees table (VARCHAR ID - references personal.id)
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_TIMESTAMP
540 )`,
541
542 // Works_in_store table (junction)
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 )`,
548
549 // Permissions table
550 `CREATE TABLE IF NOT EXISTS permissions (
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
558 // Order table (VARCHAR ID)
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_TIMESTAMP
570 )`,
571
572 // Order_items table
573 `CREATE TABLE IF NOT EXISTS order_items (
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
582 // Review table (VARCHAR ID)
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_TIMESTAMP
591 )`,
592
593 // Request table (VARCHAR ID)
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_TIMESTAMP
602 )`,
603
604 // Refund table (VARCHAR ID)
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_TIMESTAMP
614 )`,
615
616 // Report table (VARCHAR ID)
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_TIMESTAMP
627 )`,
628
629 // Audit_log table (SERIAL ID)
630 `CREATE TABLE IF NOT EXISTS audit_log (
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
641 // Color table (SERIAL ID)
642 `CREATE TABLE IF NOT EXISTS color (
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
649 // Image table (SERIAL ID)
650 `CREATE TABLE IF NOT EXISTS image (
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
658 // Delivery_address table (SERIAL ID)
659 `CREATE TABLE IF NOT EXISTS delivery_address (
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
670 // Roles table (SERIAL ID)
671 `CREATE TABLE IF NOT EXISTS roles (
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
678 // User_roles table (junction)
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 )`
684 ];
685
686 let index = 0;
687
688 function runNext() {
689 if (index >= createQueries.length) {
690 console.log('โœ… All tables created');
691 resolve();
692 return;
693 }
694
695 const tableName = createQueries[index].split('TABLE')[1].split('(')[0].trim().replace('IF NOT EXISTS', '').trim();
696 console.log(`Creating table: ${tableName}...`);
697
698 database.database.run(createQueries[index], [], (err) => {
699 if (err) {
700 console.error(`Error creating table: ${err.message}`);
701 reject(err);
702 return;
703 }
704 console.log(`โœ… Created table: ${tableName}`);
705 index++;
706 runNext();
707 });
708 }
709
710 runNext();
711 });
712}
713
714function createIndexes() {
715 return new Promise((resolve, reject) => {
716 console.log('๐Ÿ“Š Creating indexes...');
717
718 const indexQueries = [
719 'CREATE INDEX IF NOT EXISTS idx_product_store ON product(store_id)',
720 'CREATE INDEX IF NOT EXISTS idx_product_category ON product(category_id)',
721 'CREATE INDEX IF NOT EXISTS idx_order_client ON "order"(client_id)',
722 'CREATE INDEX IF NOT EXISTS idx_order_store ON "order"(store_id)',
723 'CREATE INDEX IF NOT EXISTS idx_order_date ON "order"(order_date)',
724 'CREATE INDEX IF NOT EXISTS idx_review_client ON review(client_id)',
725 'CREATE INDEX IF NOT EXISTS idx_review_product ON review(product_code)',
726 'CREATE INDEX IF NOT EXISTS idx_request_client ON request(client_id)',
727 'CREATE INDEX IF NOT EXISTS idx_request_store ON request(store_id)',
728 'CREATE INDEX IF NOT EXISTS idx_refund_order ON refund(order_num)',
729 'CREATE INDEX IF NOT EXISTS idx_refund_status ON refund(status)',
730 'CREATE INDEX IF NOT EXISTS idx_personal_email ON personal(email)',
731 'CREATE INDEX IF NOT EXISTS idx_client_email ON client(email)',
732 'CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)',
733 'CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)',
734 'CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log(user_id)',
735 'CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action)',
736 'CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log(created_at)',
737 'CREATE INDEX IF NOT EXISTS idx_delivery_client ON delivery_address(client_id)',
738 'CREATE INDEX IF NOT EXISTS idx_works_in_store_personal ON works_in_store(personal_id)',
739 'CREATE INDEX IF NOT EXISTS idx_works_in_store_store ON works_in_store(store_id)'
740 ];
741
742 let index = 0;
743
744 function runNext() {
745 if (index >= indexQueries.length) {
746 console.log('โœ… Indexes created');
747 resolve();
748 return;
749 }
750
751 database.database.run(indexQueries[index], [], (err) => {
752 if (err) {
753 console.log(`โš ๏ธ Index creation warning for ${indexQueries[index].substring(0, 50)}...: ${err.message}`);
754 }
755 index++;
756 runNext();
757 });
758 }
759
760 runNext();
761 });
762}
763
764function insertInitialData() {
765 return new Promise((resolve, reject) => {
766 console.log('๐Ÿ“ Inserting initial data...');
767
768 // REMOVED: Category insertion - now handled by database.ensureGeneralCategory()
769
770 // Insert admin user
771 const adminId = 'admin_' + Date.now().toString().slice(-6);
772 const adminPassword = bcrypt.hashSync('Admin123!', 10);
773
774 database.database.run(
775 `INSERT INTO users (id, username, email, password, user_type, force_password_change)
776 VALUES ($1, $2, $3, $4, $5, $6)
777 ON CONFLICT DO NOTHING`,
778 [adminId, 'admin', 'admin@handcraft.com', adminPassword, 'admin', 1],
779 (err) => {
780 if (err) {
781 console.error('Error inserting admin user:', err.message);
782 } else {
783 console.log('โœ… Admin user created');
784 }
785 }
786 );
787
788 // Insert default roles
789 const roles = [
790 { name: 'admin', description: 'System administrator' },
791 { name: 'store_owner', description: 'Store owner' },
792 { name: 'store_employee', description: 'Store employee' },
793 { name: 'client', description: 'Registered client' },
794 { name: 'guest', description: 'Unregistered guest' }
795 ];
796
797 let rolesInserted = 0;
798
799 roles.forEach(role => {
800 database.database.run(
801 `INSERT INTO roles (name, description)
802 VALUES ($1, $2)
803 ON CONFLICT DO NOTHING`,
804 [role.name, role.description],
805 (err) => {
806 if (err) {
807 console.error(`Error inserting role ${role.name}:`, err.message);
808 }
809 rolesInserted++;
810 if (rolesInserted === roles.length) {
811 console.log('โœ… Roles inserted');
812
813 // Ensure General category exists
814 database.ensureGeneralCategory((err) => {
815 if (err) {
816 console.error('Error ensuring General category:', err.message);
817 } else {
818 console.log('โœ… General category checked/created');
819 }
820 resolve();
821 });
822 }
823 }
824 );
825 });
826 });
827}
828
829// Initialize database on startup
830(async function() {
831 try {
832 await initializeDatabase();
833 console.log('โœ… Database initialization completed');
834 } catch (err) {
835 console.error('โŒ Database initialization failed:', err);
836 }
837})();
838
839const server = http.createServer((req, res) => {
840 const parsedUrl = url.parse(req.url, true);
841 const pathname = parsedUrl.pathname;
842 const ipAddress = getClientIp(req);
843
844 console.log('Request:', req.method, pathname);
845
846 res.setHeader('Access-Control-Allow-Origin', '*');
847 res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
848 res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
849
850 if (req.method === 'OPTIONS') {
851 res.writeHead(200);
852 res.end();
853 return;
854 }
855
856 if (pathname === '/' || pathname === '/index.html') {
857 serveStaticFile(res, 'index.html', 'text/html');
858 } else if (pathname === '/login.html') {
859 serveStaticFile(res, 'login.html', 'text/html');
860 } else if (pathname === '/register.html') {
861 serveStaticFile(res, 'register.html', 'text/html');
862 } else if (pathname === '/register-store.html') {
863 serveStaticFile(res, 'register-store.html', 'text/html');
864 } else if (pathname === '/dashboard.html') {
865 const cookies = parseCookies(req);
866 const sessionId = cookies.sessionId;
867
868 if (!sessionId || !sessions.has(sessionId)) {
869 res.writeHead(302, { 'Location': '/login.html' });
870 res.end();
871 return;
872 }
873
874 if (tempAdminSessions.has(sessionId)) {
875 res.writeHead(302, { 'Location': '/change-password.html?forced=true' });
876 res.end();
877 return;
878 }
879
880 serveStaticFile(res, 'dashboard.html', 'text/html');
881 } else if (pathname === '/verify-email.html') {
882 serveStaticFile(res, 'verify-email.html', 'text/html');
883 } else if (pathname === '/verify-2fa.html') {
884 serveStaticFile(res, 'verify-2fa.html', 'text/html');
885 } else if (pathname === '/admin.html') {
886 serveStaticFile(res, 'admin.html', 'text/html');
887 } else if (pathname === '/store-owner.html') {
888 serveStaticFile(res, 'store-owner.html', 'text/html');
889 } else if (pathname === '/store-employee.html') {
890 serveStaticFile(res, 'store-employee.html', 'text/html');
891 } else if (pathname === '/client-dashboard.html') {
892 serveStaticFile(res, 'client-dashboard.html', 'text/html');
893 } else if (pathname === '/products.html') {
894 serveStaticFile(res, 'products.html', 'text/html');
895 } else if (pathname === '/product-detail.html') {
896 serveStaticFile(res, 'product-detail.html', 'text/html');
897 } else if (pathname === '/checkout.html') {
898 serveStaticFile(res, 'checkout.html', 'text/html');
899 } else if (pathname === '/orders.html') {
900 serveStaticFile(res, 'orders.html', 'text/html');
901 } else if (pathname === '/reviews.html') {
902 serveStaticFile(res, 'reviews.html', 'text/html');
903 } else if (pathname === '/change-password.html') {
904 serveStaticFile(res, 'change-password.html', 'text/html');
905 } else if (pathname === '/style.css') {
906 serveStaticFile(res, 'style.css', 'text/css');
907 } else if (pathname === '/script.js') {
908 serveStaticFile(res, 'script.js', 'application/javascript');
909 }
910
911 else if (pathname === '/api/register' && req.method === 'POST') {
912 let body = '';
913 req.on('data', chunk => {
914 body += chunk.toString();
915 });
916
917 req.on('end', () => {
918 const { username, email, password, userType, firstName, lastName } = JSON.parse(body);
919
920 if (!username || !email || !password || !userType) {
921 res.writeHead(400, { 'Content-Type': 'application/json' });
922 res.end(JSON.stringify({ success: false, message: 'All fields are required' }));
923 return;
924 }
925
926 if (!validateEmail(email)) {
927 res.writeHead(400, { 'Content-Type': 'application/json' });
928 res.end(JSON.stringify({ success: false, message: 'Email is not valid' }));
929 return;
930 }
931
932 if (!validatePassword(password)) {
933 res.writeHead(400, { 'Content-Type': 'application/json' });
934 res.end(JSON.stringify({
935 success: false,
936 message: 'Password must have at least 8 characters, including uppercase, lowercase, number and special character'
937 }));
938 return;
939 }
940
941 database.getUserByUsername(username, (err, existingUser) => {
942 if (err) {
943 console.error('Error checking user:', err);
944 res.writeHead(500, { 'Content-Type': 'application/json' });
945 res.end(JSON.stringify({ success: false, message: 'Server error checking user' }));
946 return;
947 }
948
949 database.getClientByEmail(email, (err, existingClient) => {
950 if (err) {
951 console.error('Error checking client:', err);
952 }
953
954 if (existingUser || existingClient) {
955 res.writeHead(400, { 'Content-Type': 'application/json' });
956 res.end(JSON.stringify({ success: false, message: 'Username or email is already in use' }));
957 return;
958 }
959
960 const verificationCode = generateVerificationCode();
961
962 const tempUserData = {
963 username,
964 email,
965 password,
966 timestamp: Date.now(),
967 userType: userType,
968 firstName: firstName || '',
969 lastName: lastName || ''
970 };
971
972 tempUsers.set(verificationCode, tempUserData);
973 verificationCodes.set(email, { code: verificationCode, timestamp: Date.now() });
974
975 console.log(`โฐ Generated verification code for ${email}, expires in 30 seconds`);
976
977 sendVerificationEmail(email, verificationCode)
978 .then(() => {
979 console.log('โœ… Verification email sent to:', email);
980 database.logAudit(null, 'REGISTER_ATTEMPT', 'user', null, `Registration attempt for ${email} as ${userType}`, ipAddress);
981
982 res.writeHead(200, { 'Content-Type': 'application/json' });
983 res.end(JSON.stringify({
984 success: true,
985 message: 'Verification code sent to your email (expires in 30 seconds)',
986 email: email
987 }));
988 })
989 .catch(error => {
990 console.error('Error sending email:', error.message);
991 res.writeHead(200, { 'Content-Type': 'application/json' });
992 res.end(JSON.stringify({
993 success: true,
994 message: 'Verification code generated (check console, expires in 30 seconds)',
995 email: email,
996 developmentCode: verificationCode
997 }));
998 });
999 });
1000 });
1001 });
1002 }
1003
1004 else if (pathname === '/api/register-store' && req.method === 'POST') {
1005 let body = '';
1006 req.on('data', chunk => {
1007 body += chunk.toString();
1008 });
1009
1010 req.on('end', () => {
1011 const formData = JSON.parse(body);
1012
1013 const requiredFields = [
1014 'ownerFirstName', 'ownerLastName', 'ownerSSN', 'ownerEmail',
1015 'storeName', 'storeAddress', 'storeEmail', 'storeFoundingDate',
1016 'password', 'confirmPassword', 'signature'
1017 ];
1018
1019 for (const field of requiredFields) {
1020 if (!formData[field]) {
1021 res.writeHead(400, { 'Content-Type': 'application/json' });
1022 res.end(JSON.stringify({
1023 success: false,
1024 message: `Field ${field} is required`
1025 }));
1026 return;
1027 }
1028 }
1029
1030 if (!/^\d{13}$/.test(formData.ownerSSN)) {
1031 res.writeHead(400, { 'Content-Type': 'application/json' });
1032 res.end(JSON.stringify({
1033 success: false,
1034 message: 'SSN must be exactly 13 digits'
1035 }));
1036 return;
1037 }
1038
1039 const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
1040 if (!emailRegex.test(formData.ownerEmail)) {
1041 res.writeHead(400, { 'Content-Type': 'application/json' });
1042 res.end(JSON.stringify({
1043 success: false,
1044 message: 'Please enter a valid personal email address'
1045 }));
1046 return;
1047 }
1048
1049 if (!emailRegex.test(formData.storeEmail)) {
1050 res.writeHead(400, { 'Content-Type': 'application/json' });
1051 res.end(JSON.stringify({
1052 success: false,
1053 message: 'Please enter a valid store email address'
1054 }));
1055 return;
1056 }
1057
1058 if (formData.password !== formData.confirmPassword) {
1059 res.writeHead(400, { 'Content-Type': 'application/json' });
1060 res.end(JSON.stringify({
1061 success: false,
1062 message: 'Passwords do not match'
1063 }));
1064 return;
1065 }
1066
1067 if (!validatePassword(formData.password)) {
1068 res.writeHead(400, { 'Content-Type': 'application/json' });
1069 res.end(JSON.stringify({
1070 success: false,
1071 message: 'Password must have at least 8 characters, including uppercase, lowercase, number and special character'
1072 }));
1073 return;
1074 }
1075
1076 database.getPersonalByEmail(formData.ownerEmail, (err, existingPersonal) => {
1077 if (err) {
1078 console.error('Error checking personal:', err);
1079 res.writeHead(500, { 'Content-Type': 'application/json' });
1080 res.end(JSON.stringify({ success: false, message: 'Server error checking personal' }));
1081 return;
1082 }
1083
1084 if (existingPersonal) {
1085 res.writeHead(400, { 'Content-Type': 'application/json' });
1086 res.end(JSON.stringify({ success: false, message: 'Personal email is already registered' }));
1087 return;
1088 }
1089
1090 database.database.get(
1091 'SELECT store_id FROM store WHERE store_email = $1',
1092 [formData.storeEmail],
1093 (err, existingStore) => {
1094 if (err) {
1095 console.error('Error checking store:', err);
1096 res.writeHead(500, { 'Content-Type': 'application/json' });
1097 res.end(JSON.stringify({ success: false, message: 'Server error checking store' }));
1098 return;
1099 }
1100
1101 if (existingStore) {
1102 res.writeHead(400, { 'Content-Type': 'application/json' });
1103 res.end(JSON.stringify({ success: false, message: 'Store email is already registered' }));
1104 return;
1105 }
1106
1107 // Get the maximum store_id to determine the next store ID
1108 database.database.get(
1109 'SELECT MAX(store_id) as max_store_num FROM store',
1110 [],
1111 (err, result) => {
1112 if (err) {
1113 console.error('Error getting max store ID:', err);
1114 res.writeHead(500, { 'Content-Type': 'application/json' });
1115 res.end(JSON.stringify({ success: false, message: 'Server error generating store ID' }));
1116 return;
1117 }
1118
1119 // Next store number is max + 1, starting from 1 if no stores exist
1120 let nextStoreNumber = 1;
1121 if (result && result.max_store_num) {
1122 // Extract numeric part from store_id (format: XXX)
1123 const maxNum = parseInt(result.max_store_num, 10);
1124 if (!isNaN(maxNum)) {
1125 nextStoreNumber = maxNum + 1;
1126 }
1127 }
1128
1129 if (nextStoreNumber > 999) {
1130 res.writeHead(400, { 'Content-Type': 'application/json' });
1131 res.end(JSON.stringify({ success: false, message: 'Maximum store limit reached (999)' }));
1132 return;
1133 }
1134
1135 // Store ID is padded to 3 digits (VARCHAR)
1136 const storeIdPadded = nextStoreNumber.toString().padStart(3, '0');
1137
1138 // Personal ID is storeId + '001' (as string for display)
1139 const personalId = storeIdPadded + '001';
1140
1141 const verificationCode = generateVerificationCode();
1142
1143 const tempStoreData = {
1144 personalId: personalId, // VARCHAR for personal table
1145 ownerFirstName: formData.ownerFirstName,
1146 ownerLastName: formData.ownerLastName,
1147 ownerSSN: formData.ownerSSN,
1148 ownerEmail: formData.ownerEmail,
1149 storeId: storeIdPadded, // VARCHAR for store table
1150 storeIdPadded: storeIdPadded,
1151 storeName: formData.storeName,
1152 storeAddress: formData.storeAddress,
1153 storeEmail: formData.storeEmail,
1154 storeFoundingDate: formData.storeFoundingDate,
1155 storeDescription: formData.storeDescription || '',
1156 password: formData.password,
1157 signature: formData.signature,
1158 timestamp: Date.now()
1159 };
1160
1161 tempStoreRegistrations.set(verificationCode, tempStoreData);
1162 verificationCodes.set(formData.ownerEmail, {
1163 code: verificationCode,
1164 timestamp: Date.now(),
1165 storeRegistration: true
1166 });
1167
1168 console.log(`โฐ Generated store registration verification code for ${formData.ownerEmail}, expires in 30 seconds`);
1169 console.log(`๐Ÿช Store ID will be: ${storeIdPadded}`);
1170 console.log(`๐Ÿ‘ค Personal ID will be: ${personalId}`);
1171
1172 sendStoreRegistrationEmail(formData.ownerEmail, verificationCode, formData.storeName)
1173 .then(() => {
1174 console.log('โœ… Store registration email sent to:', formData.ownerEmail);
1175 database.logAudit(null, 'STORE_REGISTER_ATTEMPT', 'store', null, `Store registration attempt: ${formData.storeName}`, ipAddress);
1176
1177 res.writeHead(200, { 'Content-Type': 'application/json' });
1178 res.end(JSON.stringify({
1179 success: true,
1180 message: 'Verification code sent to your email (expires in 30 seconds)',
1181 email: formData.ownerEmail,
1182 storeName: formData.storeName
1183 }));
1184 })
1185 .catch(error => {
1186 console.error('Error sending store registration email:', error.message);
1187 res.writeHead(200, { 'Content-Type': 'application/json' });
1188 res.end(JSON.stringify({
1189 success: true,
1190 message: 'Verification code generated (check console, expires in 30 seconds)',
1191 email: formData.ownerEmail,
1192 storeName: formData.storeName,
1193 developmentCode: verificationCode
1194 }));
1195 });
1196 }
1197 );
1198 }
1199 );
1200 });
1201 });
1202 }
1203
1204 else if (pathname === '/api/client-register' && req.method === 'POST') {
1205 let body = '';
1206 req.on('data', chunk => {
1207 body += chunk.toString();
1208 });
1209
1210 req.on('end', () => {
1211 const { firstName, lastName, email, password, address, city, postcode, country, isDefaultAddress } = JSON.parse(body);
1212
1213 if (!firstName || !lastName || !email || !password) {
1214 res.writeHead(400, { 'Content-Type': 'application/json' });
1215 res.end(JSON.stringify({ success: false, message: 'First name, last name, email and password are required' }));
1216 return;
1217 }
1218
1219 if (!validateEmail(email)) {
1220 res.writeHead(400, { 'Content-Type': 'application/json' });
1221 res.end(JSON.stringify({ success: false, message: 'Email is not valid' }));
1222 return;
1223 }
1224
1225 if (!validatePassword(password)) {
1226 res.writeHead(400, { 'Content-Type': 'application/json' });
1227 res.end(JSON.stringify({
1228 success: false,
1229 message: 'Password must have at least 8 characters, including uppercase, lowercase, number and special character'
1230 }));
1231 return;
1232 }
1233
1234 database.getClientByEmail(email, (err, existingClient) => {
1235 if (err) {
1236 console.error('Error checking client:', err);
1237 res.writeHead(500, { 'Content-Type': 'application/json' });
1238 res.end(JSON.stringify({ success: false, message: 'Server error checking client' }));
1239 return;
1240 }
1241
1242 if (existingClient) {
1243 res.writeHead(400, { 'Content-Type': 'application/json' });
1244 res.end(JSON.stringify({ success: false, message: 'Email is already registered' }));
1245 return;
1246 }
1247
1248 const verificationCode = generateVerificationCode();
1249
1250 const tempUserData = {
1251 username: `${firstName} ${lastName}`,
1252 email,
1253 password,
1254 timestamp: Date.now(),
1255 userType: 'client',
1256 firstName: firstName,
1257 lastName: lastName,
1258 address: address || null,
1259 city: city || null,
1260 postcode: postcode || null,
1261 country: country || null,
1262 isDefaultAddress: isDefaultAddress || false
1263 };
1264
1265 tempUsers.set(verificationCode, tempUserData);
1266 verificationCodes.set(email, { code: verificationCode, timestamp: Date.now() });
1267
1268 console.log(`โฐ Generated verification code for client ${email}, expires in 30 seconds`);
1269
1270 sendVerificationEmail(email, verificationCode)
1271 .then(() => {
1272 console.log('โœ… Verification email sent to:', email);
1273 database.logAudit(null, 'CLIENT_REGISTER_ATTEMPT', 'client', null, `Client registration attempt for ${email}`, ipAddress);
1274
1275 res.writeHead(200, { 'Content-Type': 'application/json' });
1276 res.end(JSON.stringify({
1277 success: true,
1278 message: 'Verification code sent to your email (expires in 30 seconds)',
1279 email: email
1280 }));
1281 })
1282 .catch(error => {
1283 console.error('Error sending email:', error.message);
1284 res.writeHead(200, { 'Content-Type': 'application/json' });
1285 res.end(JSON.stringify({
1286 success: true,
1287 message: 'Verification code generated (check console, expires in 30 seconds)',
1288 email: email,
1289 developmentCode: verificationCode
1290 }));
1291 });
1292 });
1293 });
1294 }
1295
1296 else if (pathname === '/api/resend-verification' && req.method === 'POST') {
1297 let body = '';
1298 req.on('data', chunk => {
1299 body += chunk.toString();
1300 });
1301
1302 req.on('end', () => {
1303 const { email } = JSON.parse(body);
1304
1305 if (!email) {
1306 res.writeHead(400, { 'Content-Type': 'application/json' });
1307 res.end(JSON.stringify({ success: false, message: 'Email is required' }));
1308 return;
1309 }
1310
1311 const existingTempUser = Array.from(tempUsers.values()).find(user => user.email === email);
1312
1313 if (existingTempUser) {
1314 const newVerificationCode = generateVerificationCode();
1315
1316 const tempUserData = {
1317 username: existingTempUser.username,
1318 email: existingTempUser.email,
1319 password: existingTempUser.password,
1320 timestamp: Date.now(),
1321 userType: existingTempUser.userType,
1322 firstName: existingTempUser.firstName || '',
1323 lastName: existingTempUser.lastName || '',
1324 address: existingTempUser.address || null,
1325 city: existingTempUser.city || null,
1326 postcode: existingTempUser.postcode || null,
1327 country: existingTempUser.country || null,
1328 isDefaultAddress: existingTempUser.isDefaultAddress || false
1329 };
1330
1331 tempUsers.forEach((value, key) => {
1332 if (value.email === email) {
1333 tempUsers.delete(key);
1334 }
1335 });
1336
1337 tempUsers.set(newVerificationCode, tempUserData);
1338 verificationCodes.set(email, { code: newVerificationCode, timestamp: Date.now() });
1339
1340 console.log(`๐Ÿ”„ Resent verification code for ${email}, expires in 30 seconds`);
1341
1342 sendVerificationEmail(email, newVerificationCode)
1343 .then(() => {
1344 res.writeHead(200, { 'Content-Type': 'application/json' });
1345 res.end(JSON.stringify({
1346 success: true,
1347 message: 'New verification code sent to your email (expires in 30 seconds)',
1348 email: email
1349 }));
1350 })
1351 .catch(error => {
1352 console.error('Error sending email:', error.message);
1353 res.writeHead(200, { 'Content-Type': 'application/json' });
1354 res.end(JSON.stringify({
1355 success: true,
1356 message: 'New verification code generated (check console, expires in 30 seconds)',
1357 email: email,
1358 developmentCode: newVerificationCode
1359 }));
1360 });
1361
1362 return;
1363 }
1364
1365 const existingTempStore = Array.from(tempStoreRegistrations.values()).find(store => store.ownerEmail === email);
1366
1367 if (existingTempStore) {
1368 const newVerificationCode = generateVerificationCode();
1369
1370 const tempStoreData = {
1371 personalId: existingTempStore.personalId,
1372 ownerFirstName: existingTempStore.ownerFirstName,
1373 ownerLastName: existingTempStore.ownerLastName,
1374 ownerSSN: existingTempStore.ownerSSN,
1375 ownerEmail: existingTempStore.ownerEmail,
1376 storeId: existingTempStore.storeId,
1377 storeIdPadded: existingTempStore.storeIdPadded,
1378 storeName: existingTempStore.storeName,
1379 storeAddress: existingTempStore.storeAddress,
1380 storeEmail: existingTempStore.storeEmail,
1381 storeFoundingDate: existingTempStore.storeFoundingDate,
1382 storeDescription: existingTempStore.storeDescription,
1383 password: existingTempStore.password,
1384 signature: existingTempStore.signature,
1385 timestamp: Date.now()
1386 };
1387
1388 tempStoreRegistrations.forEach((value, key) => {
1389 if (value.ownerEmail === email) {
1390 tempStoreRegistrations.delete(key);
1391 }
1392 });
1393
1394 tempStoreRegistrations.set(newVerificationCode, tempStoreData);
1395 verificationCodes.set(email, {
1396 code: newVerificationCode,
1397 timestamp: Date.now(),
1398 storeRegistration: true
1399 });
1400
1401 console.log(`๐Ÿ”„ Resent store registration verification code for ${email}, expires in 30 seconds`);
1402
1403 sendStoreRegistrationEmail(email, newVerificationCode, existingTempStore.storeName)
1404 .then(() => {
1405 res.writeHead(200, { 'Content-Type': 'application/json' });
1406 res.end(JSON.stringify({
1407 success: true,
1408 message: 'New verification code sent to your email (expires in 30 seconds)',
1409 email: email
1410 }));
1411 })
1412 .catch(error => {
1413 console.error('Error sending store registration email:', error.message);
1414 res.writeHead(200, { 'Content-Type': 'application/json' });
1415 res.end(JSON.stringify({
1416 success: true,
1417 message: 'New verification code generated (check console, expires in 30 seconds)',
1418 email: email,
1419 developmentCode: newVerificationCode
1420 }));
1421 });
1422
1423 return;
1424 }
1425
1426 res.writeHead(400, { 'Content-Type': 'application/json' });
1427 res.end(JSON.stringify({ success: false, message: 'No pending registration found for this email' }));
1428 });
1429 }
1430
1431 else if (pathname === '/api/verify-email' && req.method === 'POST') {
1432 let body = '';
1433 req.on('data', chunk => {
1434 body += chunk.toString();
1435 });
1436
1437 req.on('end', () => {
1438 const { email, code } = JSON.parse(body);
1439
1440 if (!email || !code) {
1441 res.writeHead(400, { 'Content-Type': 'application/json' });
1442 res.end(JSON.stringify({ success: false, message: 'All fields are required' }));
1443 return;
1444 }
1445
1446 const verificationData = verificationCodes.get(email);
1447
1448 if (verificationData && verificationData.storeRegistration) {
1449 const tempStoreData = tempStoreRegistrations.get(code);
1450
1451 if (!tempStoreData || tempStoreData.ownerEmail !== email) {
1452 res.writeHead(400, { 'Content-Type': 'application/json' });
1453 res.end(JSON.stringify({ success: false, message: 'Invalid verification code' }));
1454 return;
1455 }
1456
1457 if (Date.now() - tempStoreData.timestamp > 30 * 1000) {
1458 tempStoreRegistrations.delete(code);
1459 verificationCodes.delete(email);
1460 res.writeHead(400, { 'Content-Type': 'application/json' });
1461 res.end(JSON.stringify({ success: false, message: 'Verification code has expired. Please request a new one.' }));
1462 return;
1463 }
1464
1465 database.database.run('BEGIN TRANSACTION', (err) => {
1466 if (err) {
1467 console.error('Error beginning transaction:', err);
1468 res.writeHead(500, { 'Content-Type': 'application/json' });
1469 res.end(JSON.stringify({ success: false, message: 'Server error during registration' }));
1470 return;
1471 }
1472
1473 // Insert into store table (store_id is VARCHAR)
1474 database.database.run(
1475 'INSERT INTO store (store_id, name, date_of_founding, physical_address, store_email, rating) VALUES ($1, $2, $3, $4, $5, $6)',
1476 [
1477 tempStoreData.storeId,
1478 tempStoreData.storeName,
1479 tempStoreData.storeFoundingDate,
1480 tempStoreData.storeAddress,
1481 tempStoreData.storeEmail,
1482 0.0
1483 ],
1484 function(err) {
1485 if (err) {
1486 database.database.run('ROLLBACK');
1487 console.error('Error inserting store:', err);
1488 res.writeHead(400, { 'Content-Type': 'application/json' });
1489 res.end(JSON.stringify({ success: false, message: 'Error registering store' }));
1490 return;
1491 }
1492
1493 // Insert into personal table (id is VARCHAR)
1494 database.database.run(
1495 'INSERT INTO personal (id, first_name, last_name, ssn, email, password) VALUES ($1, $2, $3, $4, $5, $6)',
1496 [
1497 tempStoreData.personalId,
1498 tempStoreData.ownerFirstName,
1499 tempStoreData.ownerLastName,
1500 tempStoreData.ownerSSN,
1501 tempStoreData.ownerEmail,
1502 bcrypt.hashSync(tempStoreData.password, 10)
1503 ],
1504 function(err) {
1505 if (err) {
1506 database.database.run('ROLLBACK');
1507 console.error('Error inserting personal:', err);
1508 if (err.code === '23505') {
1509 res.writeHead(400, { 'Content-Type': 'application/json' });
1510 res.end(JSON.stringify({
1511 success: false,
1512 message: 'This personal ID is already taken. Please try again.'
1513 }));
1514 } else {
1515 res.writeHead(400, { 'Content-Type': 'application/json' });
1516 res.end(JSON.stringify({ success: false, message: 'Error registering personal information' }));
1517 }
1518 return;
1519 }
1520
1521 // Insert into boss table (boss_id is VARCHAR, references personal.id)
1522 database.database.run(
1523 'INSERT INTO boss (boss_id, signature) VALUES ($1, $2)',
1524 [tempStoreData.personalId, tempStoreData.signature],
1525 (err) => {
1526 if (err) {
1527 database.database.run('ROLLBACK');
1528 console.error('Error inserting boss:', err);
1529 res.writeHead(400, { 'Content-Type': 'application/json' });
1530 res.end(JSON.stringify({ success: false, message: 'Error registering as boss' }));
1531 return;
1532 }
1533
1534 // Insert into works_in_store table (personal_id is VARCHAR, store_id is VARCHAR)
1535 database.database.run(
1536 'INSERT INTO works_in_store (personal_id, store_id) VALUES ($1, $2)',
1537 [tempStoreData.personalId, tempStoreData.storeId],
1538 (err) => {
1539 if (err) {
1540 database.database.run('ROLLBACK');
1541 console.error('Error inserting works_in_store:', err);
1542 res.writeHead(400, { 'Content-Type': 'application/json' });
1543 res.end(JSON.stringify({ success: false, message: 'Error assigning to store' }));
1544 return;
1545 }
1546
1547 // Insert into permissions table (personal_id is VARCHAR)
1548 database.database.run(
1549 'INSERT INTO permissions (personal_id, type, authorisation) VALUES ($1, $2, $3)',
1550 [tempStoreData.personalId, 'BOSS', 'full_access'],
1551 (err) => {
1552 if (err) {
1553 console.error('Error inserting permissions:', err);
1554 }
1555
1556 database.database.run('COMMIT', (commitErr) => {
1557 if (commitErr) {
1558 console.error('Error committing transaction:', commitErr);
1559 database.database.run('ROLLBACK');
1560 res.writeHead(500, { 'Content-Type': 'application/json' });
1561 res.end(JSON.stringify({ success: false, message: 'Error completing registration' }));
1562 return;
1563 }
1564
1565 tempStoreRegistrations.delete(code);
1566 verificationCodes.delete(email);
1567
1568 console.log(`โœ… Store registration completed successfully:`);
1569 console.log(` Store ID: ${tempStoreData.storeId}`);
1570 console.log(` Store Name: ${tempStoreData.storeName}`);
1571 console.log(` Personal ID: ${tempStoreData.personalId}`);
1572 console.log(` Owner: ${tempStoreData.ownerFirstName} ${tempStoreData.ownerLastName}`);
1573
1574 database.logAudit(tempStoreData.personalId, 'STORE_REGISTER_SUCCESS', 'store', tempStoreData.storeId, `Store registered: ${tempStoreData.storeName}`, ipAddress);
1575
1576 res.writeHead(200, { 'Content-Type': 'application/json' });
1577 res.end(JSON.stringify({
1578 success: true,
1579 message: 'Store registration successful! You can now login.',
1580 storeId: tempStoreData.storeId,
1581 storeIdPadded: tempStoreData.storeIdPadded,
1582 storeName: tempStoreData.storeName,
1583 personalId: tempStoreData.personalId,
1584 userType: 'store_owner',
1585 redirectTo: 'login.html'
1586 }));
1587 });
1588 }
1589 );
1590 }
1591 );
1592 }
1593 );
1594 }
1595 );
1596 }
1597 );
1598 });
1599
1600 return;
1601 }
1602
1603 const tempUserData = tempUsers.get(code);
1604
1605 if (!tempUserData || tempUserData.email !== email) {
1606 res.writeHead(400, { 'Content-Type': 'application/json' });
1607 res.end(JSON.stringify({ success: false, message: 'Invalid verification code' }));
1608 return;
1609 }
1610
1611 if (Date.now() - tempUserData.timestamp > 30 * 1000) {
1612 tempUsers.delete(code);
1613 verificationCodes.delete(email);
1614 res.writeHead(400, { 'Content-Type': 'application/json' });
1615 res.end(JSON.stringify({ success: false, message: 'Verification code has expired. Please request a new one.' }));
1616 return;
1617 }
1618
1619 if (tempUserData.userType === 'client') {
1620 database.createClient({
1621 first_name: tempUserData.firstName || tempUserData.username.split(' ')[0] || '',
1622 last_name: tempUserData.lastName || tempUserData.username.split(' ')[1] || '',
1623 email: tempUserData.email,
1624 password: tempUserData.password
1625 }, (err, clientId) => {
1626 if (err) {
1627 console.error('Error creating client:', err);
1628 res.writeHead(400, { 'Content-Type': 'application/json' });
1629 res.end(JSON.stringify({ success: false, message: 'Registration failed' }));
1630 } else {
1631 if (tempUserData.address && tempUserData.city && tempUserData.postcode && tempUserData.country) {
1632 database.database.run(
1633 'INSERT INTO delivery_address (client_id, address, city, postcode, country, is_default) VALUES ($1, $2, $3, $4, $5, $6)',
1634 [
1635 clientId,
1636 tempUserData.address,
1637 tempUserData.city,
1638 tempUserData.postcode,
1639 tempUserData.country,
1640 tempUserData.isDefaultAddress ? 1 : 0
1641 ],
1642 (err) => {
1643 if (err) {
1644 console.error('Error saving delivery address:', err);
1645 }
1646 }
1647 );
1648 }
1649
1650 tempUsers.delete(code);
1651 verificationCodes.delete(email);
1652
1653 database.logAudit(clientId, 'REGISTER_SUCCESS', 'client', clientId.toString(), 'Client registered', ipAddress);
1654
1655 res.writeHead(200, { 'Content-Type': 'application/json' });
1656 res.end(JSON.stringify({
1657 success: true,
1658 message: 'Successfully registered! You can now login.',
1659 userId: clientId,
1660 userType: 'client',
1661 redirectTo: 'login.html'
1662 }));
1663 }
1664 });
1665 } else {
1666 const userId = 'user_' + Date.now().toString().slice(-8);
1667
1668 database.createUser(userId, tempUserData.username, tempUserData.email, tempUserData.password, tempUserData.userType, (err, userId) => {
1669 if (err) {
1670 console.error('Error creating user:', err);
1671 res.writeHead(400, { 'Content-Type': 'application/json' });
1672 res.end(JSON.stringify({ success: false, message: 'Registration failed' }));
1673 } else {
1674 tempUsers.delete(code);
1675 verificationCodes.delete(email);
1676
1677 database.logAudit(userId, 'REGISTER_SUCCESS', 'user', userId.toString(), `User registered as ${tempUserData.userType}`, ipAddress);
1678
1679 res.writeHead(200, { 'Content-Type': 'application/json' });
1680 res.end(JSON.stringify({
1681 success: true,
1682 message: 'Successfully registered! You can now login.',
1683 userId: userId,
1684 userType: tempUserData.userType,
1685 redirectTo: 'login.html'
1686 }));
1687 }
1688 });
1689 }
1690 });
1691 }
1692
1693 else if (pathname === '/api/login' && req.method === 'POST') {
1694 let body = '';
1695 req.on('data', chunk => {
1696 body += chunk.toString();
1697 });
1698
1699 req.on('end', () => {
1700 const { email, password } = JSON.parse(body);
1701
1702 console.log(`๐Ÿ” Login attempt for email: ${email}`);
1703
1704 // First check if it's a client
1705 database.getClientByEmail(email, (err, client) => {
1706 if (err) {
1707 console.error('Error checking client:', err);
1708 }
1709
1710 if (client) {
1711 console.log(`๐Ÿ” Found client: ${client.email}`);
1712
1713 if (!client.password) {
1714 console.log('โŒ Client has no password set');
1715 database.logAudit(client.client_ID, 'LOGIN_FAILED', 'auth', client.client_ID?.toString() || 'unknown', 'Client has no password', ipAddress);
1716 res.writeHead(401, { 'Content-Type': 'application/json' });
1717 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
1718 return;
1719 }
1720
1721 database.verifyClientPassword(password, client.password, (err, isValid) => {
1722 if (err || !isValid) {
1723 const clientId = client.client_ID || 'unknown';
1724 database.logAudit(clientId, 'LOGIN_FAILED', 'auth',
1725 typeof clientId === 'string' ? clientId : String(clientId),
1726 'Invalid password for client', ipAddress);
1727 res.writeHead(401, { 'Content-Type': 'application/json' });
1728 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
1729 return;
1730 }
1731
1732 // Clients go directly to dashboard (no 2FA)
1733 const sessionId = generateSessionId();
1734 const clientId = client.client_ID;
1735
1736 sessions.set(sessionId, `client_${clientId}`);
1737
1738 console.log(`โœ… Client login successful. Session: ${sessionId}, User: client_${clientId}`);
1739
1740 database.logAudit(clientId, 'LOGIN_SUCCESS', 'auth',
1741 typeof clientId === 'string' ? clientId : String(clientId),
1742 'Client logged in successfully', ipAddress);
1743
1744 res.writeHead(200, {
1745 'Content-Type': 'application/json',
1746 'Set-Cookie': `sessionId=${sessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=Strict`
1747 });
1748 res.end(JSON.stringify({
1749 success: true,
1750 message: 'Successfully logged in',
1751 user: {
1752 id: clientId,
1753 firstName: client.first_name,
1754 lastName: client.last_name,
1755 email: client.email,
1756 userType: 'client'
1757 },
1758 redirectTo: 'client-dashboard.html'
1759 }));
1760 });
1761 return;
1762 }
1763
1764 // If not client, check personal table
1765 database.getPersonalByEmail(email, (err, personal) => {
1766 if (err) {
1767 console.error('Error checking personal:', err);
1768 }
1769
1770 if (personal) {
1771 console.log(`๐Ÿ” Found personal user: ${personal.email}`);
1772
1773 if (!personal.password) {
1774 console.log('โŒ Personal has no password set');
1775 database.logAudit(personal.id, 'LOGIN_FAILED', 'auth', personal.id, 'Personal has no password', ipAddress);
1776 res.writeHead(401, { 'Content-Type': 'application/json' });
1777 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
1778 return;
1779 }
1780
1781 database.verifyClientPassword(password, personal.password, (err, isValid) => {
1782 if (err || !isValid) {
1783 database.logAudit(personal.id, 'LOGIN_FAILED', 'auth', personal.id, 'Invalid password for personal', ipAddress);
1784 res.writeHead(401, { 'Content-Type': 'application/json' });
1785 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
1786 return;
1787 }
1788
1789 // Check if this is a boss (store owner)
1790 database.database.get(
1791 'SELECT boss_id FROM boss WHERE boss_id = ?',
1792 [personal.id],
1793 (err, boss) => {
1794 if (err) {
1795 console.error('Error checking boss status:', err);
1796 }
1797
1798 if (boss) {
1799 // This is a store owner
1800 // Check if first time login from users table
1801 database.database.get(
1802 'SELECT force_password_change FROM users WHERE email = ?',
1803 [email],
1804 (err, user) => {
1805 const isFirstTimeLogin = user && user.force_password_change === 1;
1806
1807 const twoFACode = generateVerificationCode();
1808
1809 verificationCodes.set(personal.email, {
1810 code: twoFACode,
1811 timestamp: Date.now(),
1812 userId: personal.id,
1813 isFirstTimeLogin: isFirstTimeLogin,
1814 userType: 'store_owner',
1815 needsPasswordChange: isFirstTimeLogin
1816 });
1817
1818 console.log(`โฐ Generated 2FA code for store owner ${personal.email}`);
1819
1820 send2FACode(personal.email, twoFACode)
1821 .then(() => {
1822 res.writeHead(200, { 'Content-Type': 'application/json' });
1823 res.end(JSON.stringify({
1824 success: true,
1825 message: 'Two-factor authentication code sent to your email',
1826 requires2FA: true,
1827 email: personal.email,
1828 isFirstTimeLogin: isFirstTimeLogin,
1829 userType: 'store_owner'
1830 }));
1831 })
1832 .catch(error => {
1833 console.error('Error sending 2FA email:', error);
1834 res.writeHead(200, { 'Content-Type': 'application/json' });
1835 res.end(JSON.stringify({
1836 success: true,
1837 message: 'Two-factor authentication required',
1838 requires2FA: true,
1839 email: personal.email,
1840 isFirstTimeLogin: isFirstTimeLogin,
1841 userType: 'store_owner',
1842 developmentCode: twoFACode
1843 }));
1844 });
1845 }
1846 );
1847 return;
1848 }
1849
1850 // Check if this is an employee
1851 database.database.get(
1852 'SELECT employee_id FROM employees WHERE employee_id = ?',
1853 [personal.id],
1854 (err, employee) => {
1855 if (err) {
1856 console.error('Error checking employee status:', err);
1857 }
1858
1859 if (employee) {
1860 // This is an employee
1861 database.database.get(
1862 'SELECT force_password_change FROM users WHERE email = ?',
1863 [email],
1864 (err, user) => {
1865 const isFirstTimeLogin = user && user.force_password_change === 1;
1866
1867 const twoFACode = generateVerificationCode();
1868
1869 verificationCodes.set(personal.email, {
1870 code: twoFACode,
1871 timestamp: Date.now(),
1872 userId: personal.id,
1873 isFirstTimeLogin: isFirstTimeLogin,
1874 userType: 'store_employee',
1875 needsPasswordChange: isFirstTimeLogin
1876 });
1877
1878 console.log(`โฐ Generated 2FA code for employee ${personal.email}`);
1879
1880 send2FACode(personal.email, twoFACode)
1881 .then(() => {
1882 res.writeHead(200, { 'Content-Type': 'application/json' });
1883 res.end(JSON.stringify({
1884 success: true,
1885 message: 'Two-factor authentication code sent to your email',
1886 requires2FA: true,
1887 email: personal.email,
1888 isFirstTimeLogin: isFirstTimeLogin,
1889 userType: 'store_employee'
1890 }));
1891 })
1892 .catch(error => {
1893 console.error('Error sending 2FA email:', error);
1894 res.writeHead(200, { 'Content-Type': 'application/json' });
1895 res.end(JSON.stringify({
1896 success: true,
1897 message: 'Two-factor authentication required',
1898 requires2FA: true,
1899 email: personal.email,
1900 isFirstTimeLogin: isFirstTimeLogin,
1901 userType: 'store_employee',
1902 developmentCode: twoFACode
1903 }));
1904 });
1905 }
1906 );
1907 return;
1908 }
1909
1910 // If we get here, it's a personal record without boss/employee status
1911 // Treat as regular user
1912 database.database.get(
1913 'SELECT * FROM users WHERE email = ?',
1914 [email],
1915 (err, user) => {
1916 if (err || !user) {
1917 database.getUserByUsername(email, (err, userByUsername) => {
1918 if (err || !userByUsername) {
1919 database.logAudit(null, 'LOGIN_FAILED', 'auth', null, `Failed login attempt for email: ${email}`, ipAddress);
1920 res.writeHead(401, { 'Content-Type': 'application/json' });
1921 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
1922 return;
1923 }
1924
1925 if (database.verifyPassword(password, userByUsername.password)) {
1926 const isAdminUser = userByUsername.username === 'admin';
1927 const isFirstTimeLogin = isAdminUser && userByUsername.force_password_change === 1;
1928
1929 const twoFACode = generateVerificationCode();
1930
1931 verificationCodes.set(userByUsername.email, {
1932 code: twoFACode,
1933 timestamp: Date.now(),
1934 userId: userByUsername.id,
1935 isFirstTimeLogin: isFirstTimeLogin,
1936 userType: isAdminUser ? 'admin' : userByUsername.user_type,
1937 needsPasswordChange: isFirstTimeLogin
1938 });
1939
1940 send2FACode(userByUsername.email, twoFACode)
1941 .then(() => {
1942 res.writeHead(200, { 'Content-Type': 'application/json' });
1943 res.end(JSON.stringify({
1944 success: true,
1945 message: 'Two-factor authentication code sent to your email',
1946 requires2FA: true,
1947 email: userByUsername.email,
1948 username: userByUsername.username,
1949 isFirstTimeLogin: isFirstTimeLogin,
1950 userType: isAdminUser ? 'admin' : userByUsername.user_type
1951 }));
1952 })
1953 .catch(error => {
1954 console.error('Error sending 2FA email:', error);
1955 res.writeHead(200, { 'Content-Type': 'application/json' });
1956 res.end(JSON.stringify({
1957 success: true,
1958 message: 'Two-factor authentication required',
1959 requires2FA: true,
1960 email: userByUsername.email,
1961 username: userByUsername.username,
1962 isFirstTimeLogin: isFirstTimeLogin,
1963 userType: isAdminUser ? 'admin' : userByUsername.user_type,
1964 developmentCode: twoFACode
1965 }));
1966 });
1967 } else {
1968 database.logAudit(userByUsername.id, 'LOGIN_FAILED', 'auth', userByUsername.id.toString(), 'Invalid password', ipAddress);
1969 res.writeHead(401, { 'Content-Type': 'application/json' });
1970 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
1971 }
1972 });
1973 return;
1974 }
1975
1976 if (database.verifyPassword(password, user.password)) {
1977 const isAdminUser = user.username === 'admin';
1978 const isFirstTimeLogin = isAdminUser && user.force_password_change === 1;
1979
1980 const twoFACode = generateVerificationCode();
1981
1982 verificationCodes.set(user.email, {
1983 code: twoFACode,
1984 timestamp: Date.now(),
1985 userId: user.id,
1986 isFirstTimeLogin: isFirstTimeLogin,
1987 userType: isAdminUser ? 'admin' : user.user_type,
1988 needsPasswordChange: isFirstTimeLogin
1989 });
1990
1991 send2FACode(user.email, twoFACode)
1992 .then(() => {
1993 res.writeHead(200, { 'Content-Type': 'application/json' });
1994 res.end(JSON.stringify({
1995 success: true,
1996 message: 'Two-factor authentication code sent to your email',
1997 requires2FA: true,
1998 email: user.email,
1999 username: user.username,
2000 isFirstTimeLogin: isFirstTimeLogin,
2001 userType: isAdminUser ? 'admin' : user.user_type
2002 }));
2003 })
2004 .catch(error => {
2005 console.error('Error sending 2FA email:', error);
2006 res.writeHead(200, { 'Content-Type': 'application/json' });
2007 res.end(JSON.stringify({
2008 success: true,
2009 message: 'Two-factor authentication required',
2010 requires2FA: true,
2011 email: user.email,
2012 username: user.username,
2013 isFirstTimeLogin: isFirstTimeLogin,
2014 userType: isAdminUser ? 'admin' : user.user_type,
2015 developmentCode: twoFACode
2016 }));
2017 });
2018 } else {
2019 database.logAudit(user.id, 'LOGIN_FAILED', 'auth', user.id.toString(), 'Invalid password', ipAddress);
2020 res.writeHead(401, { 'Content-Type': 'application/json' });
2021 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
2022 }
2023 }
2024 );
2025 }
2026 );
2027 }
2028 );
2029 });
2030 return;
2031 }
2032
2033 // No user found in any table
2034 database.logAudit(null, 'LOGIN_FAILED', 'auth', null, `Failed login attempt for email: ${email}`, ipAddress);
2035 res.writeHead(401, { 'Content-Type': 'application/json' });
2036 res.end(JSON.stringify({ success: false, message: 'Invalid email or password' }));
2037 });
2038 });
2039 });
2040 }
2041
2042 else if (pathname === '/api/resend-2fa' && req.method === 'POST') {
2043 let body = '';
2044 req.on('data', chunk => {
2045 body += chunk.toString();
2046 });
2047
2048 req.on('end', () => {
2049 const { email } = JSON.parse(body);
2050
2051 if (!email) {
2052 res.writeHead(400, { 'Content-Type': 'application/json' });
2053 res.end(JSON.stringify({ success: false, message: 'Email is required' }));
2054 return;
2055 }
2056
2057 database.database.get(
2058 'SELECT * FROM users WHERE email = $1',
2059 [email],
2060 (err, user) => {
2061 if (err || !user) {
2062 database.getUserByUsername(email, (err, userByUsername) => {
2063 if (err || !userByUsername) {
2064 res.writeHead(400, { 'Content-Type': 'application/json' });
2065 res.end(JSON.stringify({ success: false, message: 'User not found' }));
2066 return;
2067 }
2068
2069 const newTwoFACode = generateVerificationCode();
2070
2071 verificationCodes.set(userByUsername.email, {
2072 code: newTwoFACode,
2073 timestamp: Date.now(),
2074 userId: userByUsername.id,
2075 isAdmin: userByUsername.username === 'admin' && userByUsername.force_password_change === 1,
2076 needsPasswordChange: userByUsername.username === 'admin' && userByUsername.force_password_change === 1,
2077 userType: userByUsername.user_type
2078 });
2079
2080 console.log(`๐Ÿ”„ Resent 2FA code for ${userByUsername.email}, expires in 30 seconds`);
2081
2082 send2FACode(userByUsername.email, newTwoFACode)
2083 .then(() => {
2084 res.writeHead(200, { 'Content-Type': 'application/json' });
2085 res.end(JSON.stringify({
2086 success: true,
2087 message: 'New two-factor authentication code sent to your email (expires in 30 seconds)',
2088 email: userByUsername.email
2089 }));
2090 })
2091 .catch(error => {
2092 console.error('Error sending 2FA email:', error.message);
2093 res.writeHead(200, { 'Content-Type': 'application/json' });
2094 res.end(JSON.stringify({
2095 success: true,
2096 message: 'New two-factor authentication code generated (check console, expires in 30 seconds)',
2097 email: userByUsername.email,
2098 developmentCode: newTwoFACode
2099 }));
2100 });
2101 });
2102 return;
2103 }
2104
2105 const newTwoFACode = generateVerificationCode();
2106
2107 verificationCodes.set(user.email, {
2108 code: newTwoFACode,
2109 timestamp: Date.now(),
2110 userId: user.id,
2111 isAdmin: user.username === 'admin' && user.force_password_change === 1,
2112 needsPasswordChange: user.username === 'admin' && user.force_password_change === 1,
2113 userType: user.user_type
2114 });
2115
2116 console.log(`๐Ÿ”„ Resent 2FA code for ${user.email}, expires in 30 seconds`);
2117
2118 send2FACode(user.email, newTwoFACode)
2119 .then(() => {
2120 res.writeHead(200, { 'Content-Type': 'application/json' });
2121 res.end(JSON.stringify({
2122 success: true,
2123 message: 'New two-factor authentication code sent to your email (expires in 30 seconds)',
2124 email: user.email
2125 }));
2126 })
2127 .catch(error => {
2128 console.error('Error sending 2FA email:', error.message);
2129 res.writeHead(200, { 'Content-Type': 'application/json' });
2130 res.end(JSON.stringify({
2131 success: true,
2132 message: 'New two-factor authentication code generated (check console, expires in 30 seconds)',
2133 email: user.email,
2134 developmentCode: newTwoFACode
2135 }));
2136 });
2137 }
2138 );
2139 });
2140 }
2141
2142 else if (pathname === '/api/verify-2fa' && req.method === 'POST') {
2143 let body = '';
2144 req.on('data', chunk => {
2145 body += chunk.toString();
2146 });
2147
2148 req.on('end', () => {
2149 const { email, code } = JSON.parse(body);
2150
2151 if (!email || !code) {
2152 res.writeHead(400, { 'Content-Type': 'application/json' });
2153 res.end(JSON.stringify({ success: false, message: 'All fields are required' }));
2154 return;
2155 }
2156
2157 const verificationData = verificationCodes.get(email);
2158
2159 if (!verificationData || verificationData.code !== code) {
2160 res.writeHead(400, { 'Content-Type': 'application/json' });
2161 res.end(JSON.stringify({ success: false, message: 'Invalid two-factor authentication code' }));
2162 return;
2163 }
2164
2165 if (Date.now() - verificationData.timestamp > 30 * 1000) {
2166 verificationCodes.delete(email);
2167 res.writeHead(400, { 'Content-Type': 'application/json' });
2168 res.end(JSON.stringify({ success: false, message: 'Two-factor authentication code has expired. Please request a new one.' }));
2169 return;
2170 }
2171
2172 // Check if this is a first-time login that requires password change
2173 if (verificationData.needsPasswordChange) {
2174 const tempSessionId = generateSessionId();
2175 tempAdminSessions.set(tempSessionId, verificationData.userId);
2176
2177 database.logAudit(verificationData.userId, 'LOGIN_2FA_SUCCESS_PASSWORD_CHANGE_REQUIRED', 'auth', verificationData.userId.toString(),
2178 `${verificationData.userType} first login, password change required`, ipAddress);
2179
2180 verificationCodes.delete(email);
2181
2182 res.writeHead(200, {
2183 'Content-Type': 'application/json',
2184 'Set-Cookie': `sessionId=${tempSessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=Strict`
2185 });
2186 res.end(JSON.stringify({
2187 success: true,
2188 message: 'Two-factor authentication successful. Password change required.',
2189 requiresPasswordChange: true,
2190 userType: verificationData.userType,
2191 redirectTo: 'change-password.html?forced=true'
2192 }));
2193 return;
2194 }
2195
2196 // Regular login - create session and redirect based on user type
2197 const sessionId = generateSessionId();
2198
2199 // Determine how to store the user ID in session
2200 if (verificationData.userType === 'client') {
2201 sessions.set(sessionId, `client_${verificationData.userId}`);
2202 } else if (verificationData.userType === 'store_owner' || verificationData.userType === 'store_employee') {
2203 sessions.set(sessionId, `personal_${verificationData.userId}`);
2204 } else {
2205 sessions.set(sessionId, verificationData.userId.toString());
2206 }
2207
2208 verificationCodes.delete(email);
2209
2210 database.logAudit(verificationData.userId, 'LOGIN_SUCCESS', 'auth', verificationData.userId.toString(),
2211 `${verificationData.userType} logged in successfully`, ipAddress);
2212
2213 // Determine redirect based on user type
2214 let redirectTo = '';
2215
2216 switch(verificationData.userType) {
2217 case 'client':
2218 redirectTo = 'client-dashboard.html';
2219 break;
2220 case 'store_owner':
2221 redirectTo = 'store-owner.html';
2222 break;
2223 case 'store_employee':
2224 redirectTo = 'store-employee.html';
2225 break;
2226 case 'admin':
2227 redirectTo = 'admin.html';
2228 break;
2229 default:
2230 redirectTo = 'dashboard.html';
2231 }
2232
2233 console.log(`โœ… ${verificationData.userType} login successful. Redirecting to: ${redirectTo}`);
2234
2235 res.writeHead(200, {
2236 'Content-Type': 'application/json',
2237 'Set-Cookie': `sessionId=${sessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=Strict`
2238 });
2239 res.end(JSON.stringify({
2240 success: true,
2241 message: 'Successfully logged in',
2242 userType: verificationData.userType,
2243 redirectTo: redirectTo
2244 }));
2245 });
2246 }
2247
2248 else if (pathname === '/api/logout' && req.method === 'POST') {
2249 const cookies = parseCookies(req);
2250 const sessionId = cookies.sessionId;
2251
2252 if (sessionId) {
2253 const userId = sessions.get(sessionId);
2254 if (userId) {
2255 database.logAudit(userId, 'LOGOUT', 'auth', userId.toString(), 'User logged out', ipAddress);
2256 }
2257 sessions.delete(sessionId);
2258 tempAdminSessions.delete(sessionId);
2259 }
2260
2261 res.writeHead(200, {
2262 'Content-Type': 'application/json',
2263 'Set-Cookie': 'sessionId=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict'
2264 });
2265 res.end(JSON.stringify({ success: true, message: 'Successfully logged out' }));
2266 }
2267
2268 else if (pathname === '/api/user' && req.method === 'GET') {
2269 requireAuth(req, res, (userId) => {
2270 const cookies = parseCookies(req);
2271 const sessionId = cookies.sessionId;
2272
2273 if (tempAdminSessions.has(sessionId)) {
2274 res.writeHead(200, { 'Content-Type': 'application/json' });
2275 res.end(JSON.stringify({
2276 success: true,
2277 user: {
2278 id: userId,
2279 username: 'admin',
2280 needsPasswordChange: true
2281 },
2282 isTempSession: true
2283 }));
2284 return;
2285 }
2286
2287 const userIdStr = String(userId);
2288
2289 if (userIdStr.startsWith('client_')) {
2290 const clientId = parseInt(userIdStr.replace('client_', ''));
2291
2292 database.getClientById(clientId, (err, client) => {
2293 if (err || !client) {
2294 res.writeHead(404, { 'Content-Type': 'application/json' });
2295 res.end(JSON.stringify({ success: false, message: 'User not found' }));
2296 } else {
2297 res.writeHead(200, { 'Content-Type': 'application/json' });
2298 res.end(JSON.stringify({
2299 success: true,
2300 user: {
2301 id: client.client_ID,
2302 firstName: client.first_name,
2303 lastName: client.last_name,
2304 email: client.email,
2305 userType: 'client'
2306 }
2307 }));
2308 }
2309 });
2310 }
2311
2312 else if (userIdStr.startsWith('personal_')) {
2313 const personalId = userIdStr.replace('personal_', '');
2314
2315 database.getPersonalById(personalId, (err, personal) => {
2316 if (err || !personal) {
2317 res.writeHead(404, { 'Content-Type': 'application/json' });
2318 res.end(JSON.stringify({ success: false, message: 'User not found' }));
2319 return;
2320 }
2321
2322 database.database.get(
2323 'SELECT boss_id FROM boss WHERE boss_id = $1',
2324 [personalId],
2325 (err, boss) => {
2326 if (err) {
2327 console.error('Error checking boss:', err);
2328 }
2329
2330 if (boss) {
2331 database.database.all(
2332 `SELECT s.* FROM store s
2333 JOIN works_in_store w ON s.store_id = w.store_id
2334 WHERE w.personal_id = $1`,
2335 [personalId],
2336 (err, stores) => {
2337 if (err) {
2338 console.error('Error getting stores:', err);
2339 stores = [];
2340 }
2341
2342 res.writeHead(200, { 'Content-Type': 'application/json' });
2343 res.end(JSON.stringify({
2344 success: true,
2345 user: {
2346 id: personal.id,
2347 firstName: personal.first_name,
2348 lastName: personal.last_name,
2349 email: personal.email,
2350 userType: 'store_owner',
2351 stores: stores
2352 }
2353 }));
2354 }
2355 );
2356 } else {
2357 database.database.get(
2358 'SELECT employee_id FROM employees WHERE employee_id = $1',
2359 [personalId],
2360 (err, employee) => {
2361 if (err) {
2362 console.error('Error checking employee:', err);
2363 }
2364
2365 if (employee) {
2366 database.database.all(
2367 `SELECT s.* FROM store s
2368 JOIN works_in_store w ON s.store_id = w.store_id
2369 WHERE w.personal_id = $1`,
2370 [personalId],
2371 (err, stores) => {
2372 if (err) {
2373 console.error('Error getting stores:', err);
2374 stores = [];
2375 }
2376
2377 res.writeHead(200, { 'Content-Type': 'application/json' });
2378 res.end(JSON.stringify({
2379 success: true,
2380 user: {
2381 id: personal.id,
2382 firstName: personal.first_name,
2383 lastName: personal.last_name,
2384 email: personal.email,
2385 userType: 'store_employee',
2386 stores: stores
2387 }
2388 }));
2389 }
2390 );
2391 } else {
2392 res.writeHead(404, { 'Content-Type': 'application/json' });
2393 res.end(JSON.stringify({ success: false, message: 'User type not recognized' }));
2394 }
2395 }
2396 );
2397 }
2398 }
2399 );
2400 });
2401 } else {
2402 database.getUserById(userIdStr, (err, user) => {
2403 if (err || !user) {
2404 res.writeHead(404, { 'Content-Type': 'application/json' });
2405 res.end(JSON.stringify({ success: false, message: 'User not found' }));
2406 } else {
2407 res.writeHead(200, { 'Content-Type': 'application/json' });
2408 res.end(JSON.stringify({ success: true, user }));
2409 }
2410 });
2411 }
2412 });
2413 }
2414
2415 else if (pathname === '/api/products' && req.method === 'GET') {
2416 const query = parsedUrl.query;
2417 const categoryId = query.category;
2418 const searchTerm = query.search;
2419
2420 database.getProducts(categoryId, searchTerm, (err, products) => {
2421 if (err) {
2422 res.writeHead(500, { 'Content-Type': 'application/json' });
2423 res.end(JSON.stringify({ success: false, message: 'Error fetching products' }));
2424 } else {
2425 res.writeHead(200, { 'Content-Type': 'application/json' });
2426 res.end(JSON.stringify({ success: true, products }));
2427 }
2428 });
2429 }
2430
2431 else if (pathname === '/api/product' && req.method === 'GET') {
2432 const productId = parsedUrl.query.id;
2433
2434 if (!productId) {
2435 res.writeHead(400, { 'Content-Type': 'application/json' });
2436 res.end(JSON.stringify({ success: false, message: 'Product ID is required' }));
2437 return;
2438 }
2439
2440 database.getProductById(productId, (err, product) => {
2441 if (err) {
2442 res.writeHead(500, { 'Content-Type': 'application/json' });
2443 res.end(JSON.stringify({ success: false, message: 'Error fetching product' }));
2444 } else if (!product) {
2445 res.writeHead(404, { 'Content-Type': 'application/json' });
2446 res.end(JSON.stringify({ success: false, message: 'Product not found' }));
2447 } else {
2448 res.writeHead(200, { 'Content-Type': 'application/json' });
2449 res.end(JSON.stringify({ success: true, product }));
2450 }
2451 });
2452 }
2453
2454 else if (pathname === '/api/create-category' && req.method === 'POST') {
2455 requireStoreOwner()(req, res, (personalId) => {
2456 let body = '';
2457 req.on('data', chunk => {
2458 body += chunk.toString();
2459 });
2460
2461 req.on('end', () => {
2462 const categoryData = JSON.parse(body);
2463
2464 if (!categoryData.name || !categoryData.name.trim()) {
2465 res.writeHead(400, { 'Content-Type': 'application/json' });
2466 res.end(JSON.stringify({ success: false, message: 'Category name is required' }));
2467 return;
2468 }
2469
2470 const dbCategoryData = {
2471 name: categoryData.name.trim(),
2472 description: (categoryData.description || '').trim(),
2473 parent_id: categoryData.parentId ? parseInt(categoryData.parentId) : null
2474 };
2475
2476 database.createCategory(dbCategoryData, (err, category) => {
2477 if (err) {
2478 console.error('Error creating category:', err);
2479 res.writeHead(500, { 'Content-Type': 'application/json' });
2480 res.end(JSON.stringify({ success: false, message: 'Error creating category: ' + err.message }));
2481 } else if (!category) {
2482 res.writeHead(500, { 'Content-Type': 'application/json' });
2483 res.end(JSON.stringify({ success: false, message: 'Failed to create category' }));
2484 } else {
2485 database.logAudit(personalId, 'CATEGORY_CREATED', 'category', category.id.toString(), `New category created: ${category.name}`, ipAddress);
2486
2487 res.writeHead(200, { 'Content-Type': 'application/json' });
2488 res.end(JSON.stringify({
2489 success: true,
2490 message: 'Category created successfully',
2491 category: {
2492 id: category.id,
2493 name: category.name,
2494 parent_id: category.parent_id,
2495 description: category.description
2496 }
2497 }));
2498 }
2499 });
2500 });
2501 });
2502 }
2503
2504 else if (pathname === '/api/categories' && req.method === 'GET') {
2505 database.getCategoriesWithParents((err, categories) => {
2506 if (err) {
2507 console.error('Error fetching categories:', err);
2508 database.getCategories((err, categories) => {
2509 if (err) {
2510 console.error('Error fetching categories (fallback):', err);
2511 res.writeHead(500, { 'Content-Type': 'application/json' });
2512 res.end(JSON.stringify({ success: false, message: 'Error fetching categories' }));
2513 } else {
2514 res.writeHead(200, { 'Content-Type': 'application/json' });
2515 res.end(JSON.stringify({ success: true, categories: categories || [] }));
2516 }
2517 });
2518 } else {
2519 res.writeHead(200, { 'Content-Type': 'application/json' });
2520 res.end(JSON.stringify({ success: true, categories: categories || [] }));
2521 }
2522 });
2523 }
2524
2525 else if (pathname === '/api/stores' && req.method === 'GET') {
2526 database.getStores((err, stores) => {
2527 if (err) {
2528 res.writeHead(500, { 'Content-Type': 'application/json' });
2529 res.end(JSON.stringify({ success: false, message: 'Error fetching stores' }));
2530 } else {
2531 res.writeHead(200, { 'Content-Type': 'application/json' });
2532 res.end(JSON.stringify({ success: true, stores }));
2533 }
2534 });
2535 }
2536
2537 else if (pathname === '/api/create-order' && req.method === 'POST') {
2538 requireAuth(req, res, (userId) => {
2539 let body = '';
2540 req.on('data', chunk => {
2541 body += chunk.toString();
2542 });
2543
2544 req.on('end', () => {
2545 const orderData = JSON.parse(body);
2546 const userIdStr = String(userId);
2547
2548 if (userIdStr.startsWith('client_')) {
2549 const clientId = parseInt(userIdStr.replace('client_', ''));
2550 const storeId = orderData.storeId;
2551
2552 if (!storeId) {
2553 res.writeHead(400, { 'Content-Type': 'application/json' });
2554 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
2555 return;
2556 }
2557
2558 const year = new Date().getFullYear().toString().slice(-3);
2559
2560 database.database.get(
2561 'SELECT COUNT(*) as order_count FROM "order" WHERE store_id = $1 AND EXTRACT(YEAR FROM order_date) = $2',
2562 [storeId, new Date().getFullYear()],
2563 (err, result) => {
2564 if (err) {
2565 console.error('Error counting orders:', err);
2566 res.writeHead(500, { 'Content-Type': 'application/json' });
2567 res.end(JSON.stringify({ success: false, message: 'Error generating order ID' }));
2568 return;
2569 }
2570
2571 const orderCount = result && result[0] ? parseInt(result[0].order_count) + 1 : 1;
2572 const orderNumPadded = orderCount.toString().padStart(5, '0');
2573
2574 // Format order number: storeId + year (3 digits) + orderNum (5 digits)
2575 const orderNum = storeId + year + orderNumPadded;
2576
2577 const newOrderData = {
2578 order_num: orderNum,
2579 client_id: clientId,
2580 store_id: storeId,
2581 quantity: orderData.items.reduce((sum, item) => sum + item.quantity, 0),
2582 payment_method: orderData.paymentMethod || 'credit card',
2583 discount: orderData.discount || 0,
2584 delivery_address: orderData.deliveryAddress || 'Not specified',
2585 items: orderData.items.map(item => ({
2586 product_code: item.productCode,
2587 quantity: item.quantity,
2588 price: item.price
2589 }))
2590 };
2591
2592 database.createOrderNew(newOrderData, (err, orderId) => {
2593 if (err) {
2594 res.writeHead(500, { 'Content-Type': 'application/json' });
2595 res.end(JSON.stringify({ success: false, message: 'Error creating order' }));
2596 } else {
2597 database.logAudit(clientId, 'ORDER_CREATED', 'order', orderId.toString(), 'New order created', ipAddress);
2598 res.writeHead(200, { 'Content-Type': 'application/json' });
2599 res.end(JSON.stringify({ success: true, orderId, message: 'Order created successfully' }));
2600 }
2601 });
2602 }
2603 );
2604 } else {
2605 res.writeHead(403, { 'Content-Type': 'application/json' });
2606 res.end(JSON.stringify({ success: false, message: 'Only clients can create orders' }));
2607 }
2608 });
2609 });
2610 }
2611
2612 else if (pathname === '/api/user-orders' && req.method === 'GET') {
2613 requireAuth(req, res, (userId) => {
2614 const userIdStr = String(userId);
2615
2616 if (userIdStr.startsWith('client_')) {
2617 const clientId = parseInt(userIdStr.replace('client_', ''));
2618
2619 database.getOrdersByClient(clientId, (err, orders) => {
2620 if (err) {
2621 res.writeHead(500, { 'Content-Type': 'application/json' });
2622 res.end(JSON.stringify({ success: false, message: 'Error fetching orders' }));
2623 } else {
2624 res.writeHead(200, { 'Content-Type': 'application/json' });
2625 res.end(JSON.stringify({ success: true, orders }));
2626 }
2627 });
2628 } else {
2629 res.writeHead(403, { 'Content-Type': 'application/json' });
2630 res.end(JSON.stringify({ success: false, message: 'Only clients can view orders' }));
2631 }
2632 });
2633 }
2634
2635 else if (pathname === '/api/create-review' && req.method === 'POST') {
2636 requireAuth(req, res, (userId) => {
2637 let body = '';
2638 req.on('data', chunk => {
2639 body += chunk.toString();
2640 });
2641
2642 req.on('end', () => {
2643 const reviewData = JSON.parse(body);
2644 const userIdStr = String(userId);
2645
2646 if (userIdStr.startsWith('client_')) {
2647 const clientId = parseInt(userIdStr.replace('client_', ''));
2648
2649 reviewData.client_id = clientId;
2650
2651 database.createReviewNew(reviewData, (err, reviewId) => {
2652 if (err) {
2653 res.writeHead(500, { 'Content-Type': 'application/json' });
2654 res.end(JSON.stringify({ success: false, message: 'Error creating review' }));
2655 } else {
2656 database.logAudit(clientId, 'REVIEW_CREATED', 'review', reviewId.toString(), 'New review created', ipAddress);
2657 res.writeHead(200, { 'Content-Type': 'application/json' });
2658 res.end(JSON.stringify({ success: true, reviewId, message: 'Review created successfully' }));
2659 }
2660 });
2661 } else {
2662 res.writeHead(403, { 'Content-Type': 'application/json' });
2663 res.end(JSON.stringify({ success: false, message: 'Only clients can create reviews' }));
2664 }
2665 });
2666 });
2667 }
2668
2669 else if (pathname === '/api/create-request' && req.method === 'POST') {
2670 requireAuth(req, res, (userId) => {
2671 let body = '';
2672 req.on('data', chunk => {
2673 body += chunk.toString();
2674 });
2675
2676 req.on('end', () => {
2677 const requestData = JSON.parse(body);
2678 const userIdStr = String(userId);
2679
2680 if (userIdStr.startsWith('client_')) {
2681 const clientId = parseInt(userIdStr.replace('client_', ''));
2682 const storeId = requestData.storeId;
2683
2684 if (!storeId) {
2685 res.writeHead(400, { 'Content-Type': 'application/json' });
2686 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
2687 return;
2688 }
2689
2690 const now = new Date();
2691 const month = (now.getMonth() + 1).toString().padStart(2, '0');
2692 const year = now.getFullYear().toString().slice(-3);
2693
2694 database.database.get(
2695 'SELECT COUNT(*) as request_count FROM request WHERE store_id = $1 AND EXTRACT(YEAR FROM date_and_time) = $2 AND EXTRACT(MONTH FROM date_and_time) = $3',
2696 [storeId, now.getFullYear(), now.getMonth() + 1],
2697 (err, result) => {
2698 if (err) {
2699 console.error('Error counting requests:', err);
2700 res.writeHead(500, { 'Content-Type': 'application/json' });
2701 res.end(JSON.stringify({ success: false, message: 'Error generating request ID' }));
2702 return;
2703 }
2704
2705 const requestCount = result && result[0] ? parseInt(result[0].request_count) + 1 : 1;
2706 const requestSeqPadded = requestCount.toString().padStart(2, '0');
2707
2708 // Format request number: storeId + month (2 digits) + year (3 digits) + clientId + seq (2 digits)
2709 const requestNum = storeId + month + year + clientId + requestSeqPadded;
2710
2711 const newRequestData = {
2712 request_num: requestNum,
2713 date_and_time: now.toISOString(),
2714 problem: requestData.problem,
2715 client_id: clientId,
2716 store_id: storeId
2717 };
2718
2719 database.createRequest(newRequestData, (err, requestId) => {
2720 if (err) {
2721 res.writeHead(500, { 'Content-Type': 'application/json' });
2722 res.end(JSON.stringify({ success: false, message: 'Error creating request' }));
2723 } else {
2724 database.logAudit(clientId, 'REQUEST_CREATED', 'request', requestId.toString(), 'New request created', ipAddress);
2725 res.writeHead(200, { 'Content-Type': 'application/json' });
2726 res.end(JSON.stringify({ success: true, requestId, message: 'Request created successfully' }));
2727 }
2728 });
2729 }
2730 );
2731 } else {
2732 res.writeHead(403, { 'Content-Type': 'application/json' });
2733 res.end(JSON.stringify({ success: false, message: 'Only clients can create requests' }));
2734 }
2735 });
2736 });
2737 }
2738
2739 else if (pathname === '/api/create-refund' && req.method === 'POST') {
2740 requireAuth(req, res, (userId) => {
2741 let body = '';
2742 req.on('data', chunk => {
2743 body += chunk.toString();
2744 });
2745
2746 req.on('end', () => {
2747 const refundData = JSON.parse(body);
2748 const userIdStr = String(userId);
2749
2750 if (userIdStr.startsWith('client_')) {
2751 const clientId = parseInt(userIdStr.replace('client_', ''));
2752
2753 database.database.get(
2754 'SELECT store_id FROM "order" WHERE order_num = $1',
2755 [refundData.order_num],
2756 (err, result) => {
2757 if (err || !result || result.length === 0) {
2758 res.writeHead(404, { 'Content-Type': 'application/json' });
2759 res.end(JSON.stringify({ success: false, message: 'Order not found' }));
2760 return;
2761 }
2762
2763 const storeId = result[0].store_id;
2764 const now = new Date();
2765 const month = (now.getMonth() + 1).toString().padStart(2, '0');
2766 const year = now.getFullYear().toString().slice(-3);
2767
2768 database.database.get(
2769 'SELECT COUNT(*) as refund_count FROM refund WHERE EXTRACT(YEAR FROM request_date) = $1 AND EXTRACT(MONTH FROM request_date) = $2',
2770 [now.getFullYear(), now.getMonth() + 1],
2771 (err, result) => {
2772 if (err) {
2773 console.error('Error counting refunds:', err);
2774 res.writeHead(500, { 'Content-Type': 'application/json' });
2775 res.end(JSON.stringify({ success: false, message: 'Error generating refund ID' }));
2776 return;
2777 }
2778
2779 const refundCount = result && result[0] ? parseInt(result[0].refund_count) + 1 : 1;
2780 const refundSeqPadded = refundCount.toString().padStart(2, '0');
2781
2782 // Format refund ID: storeId + month (2 digits) + year (3 digits) + seq (2 digits)
2783 const refundId = storeId + month + year + refundSeqPadded;
2784
2785 refundData.refund_id = refundId;
2786
2787 database.createRefund(refundData, (err, refundId) => {
2788 if (err) {
2789 res.writeHead(500, { 'Content-Type': 'application/json' });
2790 res.end(JSON.stringify({ success: false, message: 'Error creating refund' }));
2791 } else {
2792 database.logAudit(clientId, 'REFUND_CREATED', 'refund', refundId.toString(), 'New refund requested', ipAddress);
2793 res.writeHead(200, { 'Content-Type': 'application/json' });
2794 res.end(JSON.stringify({ success: true, refundId, message: 'Refund requested successfully' }));
2795 }
2796 });
2797 }
2798 );
2799 }
2800 );
2801 } else {
2802 res.writeHead(403, { 'Content-Type': 'application/json' });
2803 res.end(JSON.stringify({ success: false, message: 'Only clients can request refunds' }));
2804 }
2805 });
2806 });
2807 }
2808
2809 else if (pathname === '/api/add-product' && req.method === 'POST') {
2810 requireStoreOwner()(req, res, (personalId) => {
2811 let body = '';
2812 req.on('data', chunk => {
2813 body += chunk.toString();
2814 });
2815
2816 req.on('end', () => {
2817 const productData = JSON.parse(body);
2818
2819 database.database.get(
2820 'SELECT store_id FROM works_in_store WHERE personal_id = $1',
2821 [personalId],
2822 (err, bossStore) => {
2823 if (err || !bossStore) {
2824 res.writeHead(403, { 'Content-Type': 'application/json' });
2825 res.end(JSON.stringify({ success: false, message: 'Store not found for this owner' }));
2826 return;
2827 }
2828
2829 const storeId = productData.storeId || bossStore.store_id;
2830
2831 if (!storeId) {
2832 res.writeHead(400, { 'Content-Type': 'application/json' });
2833 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
2834 return;
2835 }
2836
2837 database.database.get(
2838 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
2839 [personalId, storeId],
2840 (err, ownsStore) => {
2841 if (err || !ownsStore) {
2842 res.writeHead(403, { 'Content-Type': 'application/json' });
2843 res.end(JSON.stringify({ success: false, message: 'You are not authorized to add products to this store' }));
2844 return;
2845 }
2846
2847 // FIXED: Changed SQL syntax from SUBSTRING(code FROM 4) to SUBSTR(code, 4) for SQLite compatibility
2848 database.database.get(
2849 'SELECT MAX(CAST(SUBSTR(code, 4) AS INTEGER)) as max_product_num FROM product WHERE store_id = $1',
2850 [storeId],
2851 (err, result) => {
2852 if (err) {
2853 console.error('Error getting max product number:', err);
2854 res.writeHead(500, { 'Content-Type': 'application/json' });
2855 res.end(JSON.stringify({ success: false, message: 'Error generating product code' }));
2856 return;
2857 }
2858
2859 const maxProductNum = result?.max_product_num || 0;
2860 let nextProductNum = maxProductNum + 1;
2861
2862 // Ensure product number doesn't end with 0000
2863 while (nextProductNum % 10000 === 0) {
2864 nextProductNum++;
2865 }
2866
2867 // Format product code: storeId + productNum (4 digits, padded)
2868 const productNumPadded = nextProductNum.toString().padStart(4, '0');
2869 productData.code = storeId + productNumPadded;
2870 productData.store_id = storeId;
2871
2872 database.addProduct(personalId, productData, (err, productId) => {
2873 if (err) {
2874 console.error('Error adding product:', err);
2875 res.writeHead(500, { 'Content-Type': 'application/json' });
2876 res.end(JSON.stringify({
2877 success: false,
2878 message: 'Error adding product: ' + (err.message || 'Unknown error'),
2879 details: err.toString()
2880 }));
2881 } else {
2882 database.logAudit(personalId, 'PRODUCT_ADDED', 'product', productId.toString(), 'New product added', ipAddress);
2883
2884 res.writeHead(200, { 'Content-Type': 'application/json' });
2885 res.end(JSON.stringify({
2886 success: true,
2887 productId,
2888 message: 'Product added successfully',
2889 productCode: productData.code
2890 }));
2891 }
2892 });
2893 }
2894 );
2895 }
2896 );
2897 }
2898 );
2899 });
2900 });
2901 }
2902
2903 else if (pathname === '/api/update-product' && req.method === 'POST') {
2904 requireStoreOwner()(req, res, (personalId) => {
2905 let body = '';
2906 req.on('data', chunk => {
2907 body += chunk.toString();
2908 });
2909
2910 req.on('end', () => {
2911 const productData = JSON.parse(body);
2912
2913 if (!productData.code) {
2914 res.writeHead(400, { 'Content-Type': 'application/json' });
2915 res.end(JSON.stringify({ success: false, message: 'Product code is required' }));
2916 return;
2917 }
2918
2919 database.database.get(
2920 'SELECT store_id FROM product WHERE code = $1',
2921 [productData.code],
2922 (err, product) => {
2923 if (err || !product) {
2924 res.writeHead(404, { 'Content-Type': 'application/json' });
2925 res.end(JSON.stringify({ success: false, message: 'Product not found' }));
2926 return;
2927 }
2928
2929 database.database.get(
2930 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
2931 [personalId, product.store_id],
2932 (err, ownsStore) => {
2933 if (err || !ownsStore) {
2934 res.writeHead(403, { 'Content-Type': 'application/json' });
2935 res.end(JSON.stringify({ success: false, message: 'You are not authorized to update products in this store' }));
2936 return;
2937 }
2938
2939 database.updateProduct(personalId, productData, (err, changes) => {
2940 if (err) {
2941 console.error('Error updating product:', err);
2942 res.writeHead(500, { 'Content-Type': 'application/json' });
2943 res.end(JSON.stringify({ success: false, message: 'Error updating product: ' + err.message }));
2944 } else if (changes === 0) {
2945 res.writeHead(404, { 'Content-Type': 'application/json' });
2946 res.end(JSON.stringify({ success: false, message: 'Product not found or no changes made' }));
2947 } else {
2948 database.logAudit(personalId, 'PRODUCT_UPDATED', 'product', productData.code, 'Product updated', ipAddress);
2949 res.writeHead(200, { 'Content-Type': 'application/json' });
2950 res.end(JSON.stringify({ success: true, message: 'Product updated successfully' }));
2951 }
2952 });
2953 }
2954 );
2955 }
2956 );
2957 });
2958 });
2959 }
2960
2961 else if (pathname === '/api/store-reports' && req.method === 'GET') {
2962 requireRole('store_owner')(req, res, (userId, user) => {
2963 database.getStoreReports(userId, (err, reports) => {
2964 if (err) {
2965 res.writeHead(500, { 'Content-Type': 'application/json' });
2966 res.end(JSON.stringify({ success: false, message: 'Error fetching reports' }));
2967 } else {
2968 res.writeHead(200, { 'Content-Type': 'application/json' });
2969 res.end(JSON.stringify({ success: true, reports }));
2970 }
2971 });
2972 });
2973 }
2974
2975 else if (pathname === '/api/all-users' && req.method === 'GET') {
2976 requireRole('admin')(req, res, (userId, user) => {
2977 database.getAllUsers((err, users) => {
2978 if (err) {
2979 res.writeHead(500, { 'Content-Type': 'application/json' });
2980 res.end(JSON.stringify({ success: false, message: 'Error fetching users' }));
2981 } else {
2982 res.writeHead(200, { 'Content-Type': 'application/json' });
2983 res.end(JSON.stringify({ success: true, users }));
2984 }
2985 });
2986 });
2987 }
2988
2989 else if (pathname === '/api/all-orders' && req.method === 'GET') {
2990 requireRole('admin')(req, res, (userId, user) => {
2991 database.getAllOrders((err, orders) => {
2992 if (err) {
2993 res.writeHead(500, { 'Content-Type': 'application/json' });
2994 res.end(JSON.stringify({ success: false, message: 'Error fetching orders' }));
2995 } else {
2996 res.writeHead(200, { 'Content-Type': 'application/json' });
2997 res.end(JSON.stringify({ success: true, orders }));
2998 }
2999 });
3000 });
3001 }
3002
3003 else if (pathname === '/api/force-change-password' && req.method === 'POST') {
3004 const cookies = parseCookies(req);
3005 const sessionId = cookies.sessionId;
3006 const userId = tempAdminSessions.get(sessionId);
3007
3008 if (!userId) {
3009 res.writeHead(401, { 'Content-Type': 'application/json' });
3010 res.end(JSON.stringify({ success: false, message: 'Not authenticated or invalid session' }));
3011 return;
3012 }
3013
3014 let body = '';
3015 req.on('data', chunk => {
3016 body += chunk.toString();
3017 });
3018
3019 req.on('end', () => {
3020 const { currentPassword, newPassword, confirmPassword } = JSON.parse(body);
3021
3022 if (!currentPassword || !newPassword || !confirmPassword) {
3023 res.writeHead(400, { 'Content-Type': 'application/json' });
3024 res.end(JSON.stringify({ success: false, message: 'All fields are required' }));
3025 return;
3026 }
3027
3028 if (newPassword !== confirmPassword) {
3029 res.writeHead(400, { 'Content-Type': 'application/json' });
3030 res.end(JSON.stringify({ success: false, message: 'New passwords do not match' }));
3031 return;
3032 }
3033
3034 if (!validatePassword(newPassword)) {
3035 res.writeHead(400, { 'Content-Type': 'application/json' });
3036 res.end(JSON.stringify({
3037 success: false,
3038 message: 'Password must have at least 8 characters, including uppercase, lowercase, number and special character'
3039 }));
3040 return;
3041 }
3042
3043 database.getUserByUsername('admin', (err, user) => {
3044 if (err || !user) {
3045 res.writeHead(404, { 'Content-Type': 'application/json' });
3046 res.end(JSON.stringify({ success: false, message: 'User not found' }));
3047 return;
3048 }
3049
3050 database.verifyPassword(currentPassword, user.password, (err, isValid) => {
3051 if (err || !isValid) {
3052 res.writeHead(400, { 'Content-Type': 'application/json' });
3053 res.end(JSON.stringify({ success: false, message: 'Current password is incorrect' }));
3054 return;
3055 }
3056
3057 database.updatePasswordAndClearForce(userId, newPassword, (err) => {
3058 if (err) {
3059 res.writeHead(500, { 'Content-Type': 'application/json' });
3060 res.end(JSON.stringify({ success: false, message: 'Failed to update password' }));
3061 } else {
3062 tempAdminSessions.delete(sessionId);
3063
3064 const newSessionId = generateSessionId();
3065 sessions.set(newSessionId, String(userId));
3066
3067 database.logAudit(userId, 'FORCED_PASSWORD_CHANGE', 'auth', userId.toString(),
3068 'Admin forced password change completed', ipAddress);
3069
3070 res.writeHead(200, {
3071 'Content-Type': 'application/json',
3072 'Set-Cookie': `sessionId=${newSessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=Strict`
3073 });
3074 res.end(JSON.stringify({
3075 success: true,
3076 message: 'Password changed successfully. You can now access the dashboard.',
3077 redirectTo: 'admin.html'
3078 }));
3079 }
3080 });
3081 });
3082 });
3083 });
3084 }
3085
3086 else if (pathname === '/api/register-employee' && req.method === 'POST') {
3087 requireAuth(req, res, (userId) => {
3088 const userIdStr = String(userId);
3089
3090 if (!userIdStr.startsWith('personal_')) {
3091 res.writeHead(403, { 'Content-Type': 'application/json' });
3092 res.end(JSON.stringify({ success: false, message: 'Only store owners can register employees' }));
3093 return;
3094 }
3095
3096 const personalId = userIdStr.replace('personal_', '');
3097
3098 database.database.get(
3099 'SELECT boss_id FROM boss WHERE boss_id = $1',
3100 [personalId],
3101 (err, boss) => {
3102 if (err || !boss) {
3103 res.writeHead(403, { 'Content-Type': 'application/json' });
3104 res.end(JSON.stringify({ success: false, message: 'Only store owners can register employees' }));
3105 return;
3106 }
3107
3108 let body = '';
3109 req.on('data', chunk => {
3110 body += chunk.toString();
3111 });
3112
3113 req.on('end', () => {
3114 const { firstName, lastName, ssn, email, password, storeId, dateOfHire } = JSON.parse(body);
3115
3116 if (!firstName || !lastName || !ssn || !email || !password || !storeId || !dateOfHire) {
3117 res.writeHead(400, { 'Content-Type': 'application/json' });
3118 res.end(JSON.stringify({ success: false, message: 'All fields are required' }));
3119 return;
3120 }
3121
3122 if (!/^\d{13}$/.test(ssn)) {
3123 res.writeHead(400, { 'Content-Type': 'application/json' });
3124 res.end(JSON.stringify({ success: false, message: 'SSN must be exactly 13 digits' }));
3125 return;
3126 }
3127
3128 if (!validateEmail(email)) {
3129 res.writeHead(400, { 'Content-Type': 'application/json' });
3130 res.end(JSON.stringify({ success: false, message: 'Invalid email format' }));
3131 return;
3132 }
3133
3134 if (!validatePassword(password)) {
3135 res.writeHead(400, { 'Content-Type': 'application/json' });
3136 res.end(JSON.stringify({
3137 success: false,
3138 message: 'Password must have at least 8 characters, including uppercase, lowercase, number and special character'
3139 }));
3140 return;
3141 }
3142
3143 database.getPersonalByEmail(email, (err, existingPersonal) => {
3144 if (err) {
3145 console.error('Error checking personal:', err);
3146 res.writeHead(500, { 'Content-Type': 'application/json' });
3147 res.end(JSON.stringify({ success: false, message: 'Server error checking personal' }));
3148 return;
3149 }
3150
3151 if (existingPersonal) {
3152 res.writeHead(400, { 'Content-Type': 'application/json' });
3153 res.end(JSON.stringify({ success: false, message: 'Email is already registered' }));
3154 return;
3155 }
3156
3157 // Find the next available employee number for this store
3158 database.database.all(
3159 "SELECT id FROM personal WHERE id LIKE '" + storeId + "%' ORDER BY id",
3160 [],
3161 (err, existingEmployees) => {
3162 if (err) {
3163 console.error('Error getting employees:', err);
3164 res.writeHead(500, { 'Content-Type': 'application/json' });
3165 res.end(JSON.stringify({ success: false, message: 'Server error generating employee ID' }));
3166 return;
3167 }
3168
3169 // Find the first available employee number from 001 to 999
3170 let nextEmployeeNum = 1;
3171 const existingNumbers = (existingEmployees || [])
3172 .map(e => {
3173 const num = e.id.substring(3);
3174 return parseInt(num, 10);
3175 })
3176 .filter(num => !isNaN(num));
3177
3178 existingNumbers.sort((a, b) => a - b);
3179
3180 // Find the first gap in the sequence
3181 for (let i = 1; i <= 999; i++) {
3182 if (!existingNumbers.includes(i)) {
3183 nextEmployeeNum = i;
3184 break;
3185 }
3186 }
3187
3188 if (nextEmployeeNum > 999) {
3189 res.writeHead(400, { 'Content-Type': 'application/json' });
3190 res.end(JSON.stringify({ success: false, message: 'Maximum employees reached for this store' }));
3191 return;
3192 }
3193
3194 const employeeNumPadded = nextEmployeeNum.toString().padStart(3, '0');
3195 const newPersonalId = storeId + employeeNumPadded;
3196
3197 database.database.run('BEGIN TRANSACTION', (err) => {
3198 if (err) {
3199 console.error('Error beginning transaction:', err);
3200 res.writeHead(500, { 'Content-Type': 'application/json' });
3201 res.end(JSON.stringify({ success: false, message: 'Server error during registration' }));
3202 return;
3203 }
3204
3205 database.database.run(
3206 'INSERT INTO personal (id, first_name, last_name, ssn, email, password) VALUES ($1, $2, $3, $4, $5, $6)',
3207 [
3208 newPersonalId,
3209 firstName,
3210 lastName,
3211 ssn,
3212 email,
3213 bcrypt.hashSync(password, 10)
3214 ],
3215 function(err) {
3216 if (err) {
3217 database.database.run('ROLLBACK');
3218 console.error('Error inserting personal:', err);
3219 if (err.code === '23505') {
3220 res.writeHead(400, { 'Content-Type': 'application/json' });
3221 res.end(JSON.stringify({ success: false, message: 'This personal ID is already taken. Please try again.' }));
3222 } else {
3223 res.writeHead(400, { 'Content-Type': 'application/json' });
3224 res.end(JSON.stringify({ success: false, message: 'Error registering employee' }));
3225 }
3226 return;
3227 }
3228
3229 database.database.run(
3230 'INSERT INTO employees (employee_id, date_of_hire) VALUES ($1, $2)',
3231 [newPersonalId, dateOfHire],
3232 (err) => {
3233 if (err) {
3234 database.database.run('ROLLBACK');
3235 console.error('Error inserting employee:', err);
3236 res.writeHead(400, { 'Content-Type': 'application/json' });
3237 res.end(JSON.stringify({ success: false, message: 'Error registering as employee' }));
3238 return;
3239 }
3240
3241 database.database.run(
3242 'INSERT INTO works_in_store (personal_id, store_id) VALUES ($1, $2)',
3243 [newPersonalId, storeId],
3244 (err) => {
3245 if (err) {
3246 database.database.run('ROLLBACK');
3247 console.error('Error inserting works_in_store:', err);
3248 res.writeHead(400, { 'Content-Type': 'application/json' });
3249 res.end(JSON.stringify({ success: false, message: 'Error assigning employee to store' }));
3250 return;
3251 }
3252
3253 database.database.run(
3254 'INSERT INTO permissions (personal_id, type, authorisation) VALUES ($1, $2, $3)',
3255 [newPersonalId, 'EMPLOYEE', 'limited_access'],
3256 (err) => {
3257 if (err) {
3258 console.error('Error inserting permissions:', err);
3259 }
3260
3261 database.database.run('COMMIT', (err) => {
3262 if (err) {
3263 database.database.run('ROLLBACK');
3264 console.error('Error committing transaction:', err);
3265 res.writeHead(500, { 'Content-Type': 'application/json' });
3266 res.end(JSON.stringify({ success: false, message: 'Error completing registration' }));
3267 return;
3268 }
3269
3270 database.logAudit(personalId, 'EMPLOYEE_REGISTERED', 'employee', newPersonalId, `Employee registered: ${firstName} ${lastName}`, ipAddress);
3271
3272 res.writeHead(200, { 'Content-Type': 'application/json' });
3273 res.end(JSON.stringify({
3274 success: true,
3275 message: 'Employee registered successfully!',
3276 employeeId: newPersonalId,
3277 name: `${firstName} ${lastName}`
3278 }));
3279 });
3280 }
3281 );
3282 }
3283 );
3284 }
3285 );
3286 }
3287 );
3288 });
3289 }
3290 );
3291 });
3292 });
3293 }
3294 );
3295 });
3296 }
3297
3298 else if (pathname === '/api/delete-employee' && req.method === 'POST') {
3299 requireAuth(req, res, (userId) => {
3300 const userIdStr = String(userId);
3301
3302 if (!userIdStr.startsWith('personal_')) {
3303 res.writeHead(403, { 'Content-Type': 'application/json' });
3304 res.end(JSON.stringify({ success: false, message: 'Only store owners can delete employees' }));
3305 return;
3306 }
3307
3308 const personalId = userIdStr.replace('personal_', '');
3309
3310 database.database.get(
3311 'SELECT boss_id FROM boss WHERE boss_id = $1',
3312 [personalId],
3313 (err, boss) => {
3314 if (err || !boss) {
3315 res.writeHead(403, { 'Content-Type': 'application/json' });
3316 res.end(JSON.stringify({ success: false, message: 'Only store owners can delete employees' }));
3317 return;
3318 }
3319
3320 let body = '';
3321 req.on('data', chunk => {
3322 body += chunk.toString();
3323 });
3324
3325 req.on('end', () => {
3326 const { employeeId, storeId } = JSON.parse(body);
3327
3328 if (!employeeId || !storeId) {
3329 res.writeHead(400, { 'Content-Type': 'application/json' });
3330 res.end(JSON.stringify({ success: false, message: 'Employee ID and Store ID are required' }));
3331 return;
3332 }
3333
3334 database.database.get(
3335 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3336 [personalId, storeId],
3337 (err, bossStore) => {
3338 if (err || !bossStore) {
3339 res.writeHead(403, { 'Content-Type': 'application/json' });
3340 res.end(JSON.stringify({ success: false, message: 'You are not authorized to manage employees in this store' }));
3341 return;
3342 }
3343
3344 database.database.get(
3345 'SELECT personal_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3346 [employeeId, storeId],
3347 (err, employeeStore) => {
3348 if (err || !employeeStore) {
3349 res.writeHead(404, { 'Content-Type': 'application/json' });
3350 res.end(JSON.stringify({ success: false, message: 'Employee not found in this store' }));
3351 return;
3352 }
3353
3354 database.database.get(
3355 'SELECT boss_id FROM boss WHERE boss_id = $1',
3356 [employeeId],
3357 (err, isBoss) => {
3358 if (err) {
3359 console.error('Error checking if employee is boss:', err);
3360 }
3361
3362 if (isBoss) {
3363 res.writeHead(403, { 'Content-Type': 'application/json' });
3364 res.end(JSON.stringify({ success: false, message: 'Cannot delete store owners' }));
3365 return;
3366 }
3367
3368 database.database.run('BEGIN TRANSACTION', (err) => {
3369 if (err) {
3370 console.error('Error beginning transaction:', err);
3371 res.writeHead(500, { 'Content-Type': 'application/json' });
3372 res.end(JSON.stringify({ success: false, message: 'Server error during deletion' }));
3373 return;
3374 }
3375
3376 database.database.run(
3377 'DELETE FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3378 [employeeId, storeId],
3379 (err) => {
3380 if (err) {
3381 database.database.run('ROLLBACK');
3382 console.error('Error deleting from works_in_store:', err);
3383 res.writeHead(500, { 'Content-Type': 'application/json' });
3384 res.end(JSON.stringify({ success: false, message: 'Error removing employee from store' }));
3385 return;
3386 }
3387
3388 database.database.run(
3389 'DELETE FROM employees WHERE employee_id = $1',
3390 [employeeId],
3391 (err) => {
3392 if (err) {
3393 console.error('Error deleting from employees:', err);
3394 }
3395
3396 database.database.run(
3397 'DELETE FROM permissions WHERE personal_id = $1',
3398 [employeeId],
3399 (err) => {
3400 if (err) {
3401 console.error('Error deleting from permissions:', err);
3402 }
3403
3404 database.database.run(
3405 'DELETE FROM personal WHERE id = $1',
3406 [employeeId],
3407 (err) => {
3408 if (err) {
3409 console.error('Error deleting from personal:', err);
3410 }
3411
3412 database.database.run('COMMIT', (commitErr) => {
3413 if (commitErr) {
3414 database.database.run('ROLLBACK');
3415 console.error('Error committing transaction:', commitErr);
3416 res.writeHead(500, { 'Content-Type': 'application/json' });
3417 res.end(JSON.stringify({ success: false, message: 'Error completing deletion' }));
3418 return;
3419 }
3420
3421 database.logAudit(personalId, 'EMPLOYEE_DELETED', 'employee', employeeId, `Employee deleted from store ${storeId}`, ipAddress);
3422
3423 res.writeHead(200, { 'Content-Type': 'application/json' });
3424 res.end(JSON.stringify({
3425 success: true,
3426 message: 'Employee deleted successfully'
3427 }));
3428 });
3429 }
3430 );
3431 }
3432 );
3433 }
3434 );
3435 }
3436 );
3437 });
3438 }
3439 );
3440 }
3441 );
3442 }
3443 );
3444 });
3445 }
3446 );
3447 });
3448 }
3449
3450 else if (pathname === '/api/update-employee-status' && req.method === 'POST') {
3451 requireAuth(req, res, (userId) => {
3452 const userIdStr = String(userId);
3453
3454 if (!userIdStr.startsWith('personal_')) {
3455 res.writeHead(403, { 'Content-Type': 'application/json' });
3456 res.end(JSON.stringify({ success: false, message: 'Only store owners can update employee status' }));
3457 return;
3458 }
3459
3460 const personalId = userIdStr.replace('personal_', '');
3461
3462 database.database.get(
3463 'SELECT boss_id FROM boss WHERE boss_id = $1',
3464 [personalId],
3465 (err, boss) => {
3466 if (err || !boss) {
3467 res.writeHead(403, { 'Content-Type': 'application/json' });
3468 res.end(JSON.stringify({ success: false, message: 'Only store owners can update employee status' }));
3469 return;
3470 }
3471
3472 let body = '';
3473 req.on('data', chunk => {
3474 body += chunk.toString();
3475 });
3476
3477 req.on('end', () => {
3478 const { employeeId, storeId, status } = JSON.parse(body);
3479
3480 if (!employeeId || !storeId || !status) {
3481 res.writeHead(400, { 'Content-Type': 'application/json' });
3482 res.end(JSON.stringify({ success: false, message: 'Employee ID, Store ID and Status are required' }));
3483 return;
3484 }
3485
3486 database.database.get(
3487 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3488 [personalId, storeId],
3489 (err, bossStore) => {
3490 if (err || !bossStore) {
3491 res.writeHead(403, { 'Content-Type': 'application/json' });
3492 res.end(JSON.stringify({ success: false, message: 'You are not authorized to manage employees in this store' }));
3493 return;
3494 }
3495
3496 database.database.get(
3497 'SELECT personal_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3498 [employeeId, storeId],
3499 (err, employeeStore) => {
3500 if (err || !employeeStore) {
3501 res.writeHead(404, { 'Content-Type': 'application/json' });
3502 res.end(JSON.stringify({ success: false, message: 'Employee not found in this store' }));
3503 return;
3504 }
3505
3506 let permissionType = 'EMPLOYEE';
3507 let authorization = 'limited_access';
3508
3509 if (status === 'promoted') {
3510 permissionType = 'MANAGER';
3511 authorization = 'extended_access';
3512 } else if (status === 'suspended') {
3513 permissionType = 'SUSPENDED';
3514 authorization = 'no_access';
3515 } else if (status === 'active') {
3516 permissionType = 'EMPLOYEE';
3517 authorization = 'limited_access';
3518 }
3519
3520 database.database.run(
3521 'UPDATE permissions SET type = $1, authorisation = $2 WHERE personal_id = $3',
3522 [permissionType, authorization, employeeId],
3523 function(err) {
3524 if (err) {
3525 console.error('Error updating employee status:', err);
3526 res.writeHead(500, { 'Content-Type': 'application/json' });
3527 res.end(JSON.stringify({ success: false, message: 'Error updating employee status' }));
3528 return;
3529 }
3530
3531 database.logAudit(personalId, 'EMPLOYEE_STATUS_UPDATED', 'employee', employeeId, `Employee status updated to: ${status}`, ipAddress);
3532
3533 res.writeHead(200, { 'Content-Type': 'application/json' });
3534 res.end(JSON.stringify({
3535 success: true,
3536 message: `Employee status updated to ${status} successfully`
3537 }));
3538 }
3539 );
3540 }
3541 );
3542 }
3543 );
3544 });
3545 }
3546 );
3547 });
3548 }
3549
3550 else if (pathname === '/api/update-employee' && req.method === 'POST') {
3551 requireAuth(req, res, (userId) => {
3552 const userIdStr = String(userId);
3553
3554 if (!userIdStr.startsWith('personal_')) {
3555 res.writeHead(403, { 'Content-Type': 'application/json' });
3556 res.end(JSON.stringify({ success: false, message: 'Only store owners can update employees' }));
3557 return;
3558 }
3559
3560 const personalId = userIdStr.replace('personal_', '');
3561
3562 database.database.get(
3563 'SELECT boss_id FROM boss WHERE boss_id = $1',
3564 [personalId],
3565 (err, boss) => {
3566 if (err || !boss) {
3567 res.writeHead(403, { 'Content-Type': 'application/json' });
3568 res.end(JSON.stringify({ success: false, message: 'Only store owners can update employees' }));
3569 return;
3570 }
3571
3572 let body = '';
3573 req.on('data', chunk => {
3574 body += chunk.toString();
3575 });
3576
3577 req.on('end', () => {
3578 const { employeeId, storeId, firstName, lastName, email } = JSON.parse(body);
3579
3580 if (!employeeId || !storeId) {
3581 res.writeHead(400, { 'Content-Type': 'application/json' });
3582 res.end(JSON.stringify({ success: false, message: 'Employee ID and Store ID are required' }));
3583 return;
3584 }
3585
3586 database.database.get(
3587 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3588 [personalId, storeId],
3589 (err, bossStore) => {
3590 if (err || !bossStore) {
3591 res.writeHead(403, { 'Content-Type': 'application/json' });
3592 res.end(JSON.stringify({ success: false, message: 'You are not authorized to manage employees in this store' }));
3593 return;
3594 }
3595
3596 database.database.get(
3597 'SELECT personal_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3598 [employeeId, storeId],
3599 (err, employeeStore) => {
3600 if (err || !employeeStore) {
3601 res.writeHead(404, { 'Content-Type': 'application/json' });
3602 res.end(JSON.stringify({ success: false, message: 'Employee not found in this store' }));
3603 return;
3604 }
3605
3606 const updates = [];
3607 const params = [];
3608
3609 if (firstName) {
3610 updates.push('first_name = $' + (params.length + 1));
3611 params.push(firstName);
3612 }
3613
3614 if (lastName) {
3615 updates.push('last_name = $' + (params.length + 1));
3616 params.push(lastName);
3617 }
3618
3619 if (email) {
3620 if (!validateEmail(email)) {
3621 res.writeHead(400, { 'Content-Type': 'application/json' });
3622 res.end(JSON.stringify({ success: false, message: 'Invalid email format' }));
3623 return;
3624 }
3625 updates.push('email = $' + (params.length + 1));
3626 params.push(email);
3627 }
3628
3629 if (updates.length === 0) {
3630 res.writeHead(400, { 'Content-Type': 'application/json' });
3631 res.end(JSON.stringify({ success: false, message: 'No fields to update' }));
3632 return;
3633 }
3634
3635 params.push(employeeId);
3636
3637 database.database.run(
3638 `UPDATE personal SET ${updates.join(', ')} WHERE id = $${params.length}`,
3639 params,
3640 function(err) {
3641 if (err) {
3642 console.error('Error updating employee:', err);
3643 res.writeHead(500, { 'Content-Type': 'application/json' });
3644 res.end(JSON.stringify({ success: false, message: 'Error updating employee information' }));
3645 return;
3646 }
3647
3648 database.logAudit(personalId, 'EMPLOYEE_UPDATED', 'employee', employeeId, `Employee information updated`, ipAddress);
3649
3650 res.writeHead(200, { 'Content-Type': 'application/json' });
3651 res.end(JSON.stringify({
3652 success: true,
3653 message: 'Employee information updated successfully'
3654 }));
3655 }
3656 );
3657 }
3658 );
3659 }
3660 );
3661 });
3662 }
3663 );
3664 });
3665 }
3666
3667 else if (pathname === '/api/store-products' && req.method === 'GET') {
3668 requireStoreOwner()(req, res, (personalId) => {
3669 const storeId = parsedUrl.query.storeId;
3670
3671 if (!storeId) {
3672 database.database.get(
3673 'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
3674 [personalId],
3675 (err, store) => {
3676 if (err || !store) {
3677 res.writeHead(400, { 'Content-Type': 'application/json' });
3678 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
3679 return;
3680 }
3681
3682 database.getStoreProducts(store.store_id, (err, products) => {
3683 if (err) {
3684 res.writeHead(500, { 'Content-Type': 'application/json' });
3685 res.end(JSON.stringify({ success: false, message: 'Error fetching store products' }));
3686 } else {
3687 res.writeHead(200, { 'Content-Type': 'application/json' });
3688 res.end(JSON.stringify({ success: true, products }));
3689 }
3690 });
3691 }
3692 );
3693 return;
3694 }
3695
3696 database.database.get(
3697 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3698 [personalId, storeId],
3699 (err, ownsStore) => {
3700 if (err || !ownsStore) {
3701 res.writeHead(403, { 'Content-Type': 'application/json' });
3702 res.end(JSON.stringify({ success: false, message: 'You are not authorized to view products in this store' }));
3703 return;
3704 }
3705
3706 database.getStoreProducts(storeId, (err, products) => {
3707 if (err) {
3708 res.writeHead(500, { 'Content-Type': 'application/json' });
3709 res.end(JSON.stringify({ success: false, message: 'Error fetching store products' }));
3710 } else {
3711 res.writeHead(200, { 'Content-Type': 'application/json' });
3712 res.end(JSON.stringify({ success: true, products }));
3713 }
3714 });
3715 }
3716 );
3717 });
3718 }
3719
3720 else if (pathname === '/api/store-orders' && req.method === 'GET') {
3721 requireStoreOwner()(req, res, (personalId) => {
3722 const storeId = parsedUrl.query.storeId;
3723
3724 if (!storeId) {
3725 database.database.get(
3726 'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
3727 [personalId],
3728 (err, store) => {
3729 if (err || !store) {
3730 res.writeHead(400, { 'Content-Type': 'application/json' });
3731 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
3732 return;
3733 }
3734
3735 database.getStoreOrders(store.store_id, (err, orders) => {
3736 if (err) {
3737 res.writeHead(500, { 'Content-Type': 'application/json' });
3738 res.end(JSON.stringify({ success: false, message: 'Error fetching store orders' }));
3739 } else {
3740 res.writeHead(200, { 'Content-Type': 'application/json' });
3741 res.end(JSON.stringify({ success: true, orders }));
3742 }
3743 });
3744 }
3745 );
3746 return;
3747 }
3748
3749 database.database.get(
3750 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3751 [personalId, storeId],
3752 (err, ownsStore) => {
3753 if (err || !ownsStore) {
3754 res.writeHead(403, { 'Content-Type': 'application/json' });
3755 res.end(JSON.stringify({ success: false, message: 'You are not authorized to view orders in this store' }));
3756 return;
3757 }
3758
3759 database.getStoreOrders(storeId, (err, orders) => {
3760 if (err) {
3761 res.writeHead(500, { 'Content-Type': 'application/json' });
3762 res.end(JSON.stringify({ success: false, message: 'Error fetching store orders' }));
3763 } else {
3764 res.writeHead(200, { 'Content-Type': 'application/json' });
3765 res.end(JSON.stringify({ success: true, orders }));
3766 }
3767 });
3768 }
3769 );
3770 });
3771 }
3772
3773 else if (pathname === '/api/store-employees' && req.method === 'GET') {
3774 requireStoreOwner()(req, res, (personalId) => {
3775 const storeId = parsedUrl.query.storeId;
3776
3777 if (!storeId) {
3778 database.database.get(
3779 'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
3780 [personalId],
3781 (err, store) => {
3782 if (err || !store) {
3783 res.writeHead(400, { 'Content-Type': 'application/json' });
3784 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
3785 return;
3786 }
3787
3788 database.getStoreEmployees(store.store_id, (err, employees) => {
3789 if (err) {
3790 res.writeHead(500, { 'Content-Type': 'application/json' });
3791 res.end(JSON.stringify({ success: false, message: 'Error fetching store employees' }));
3792 } else {
3793 res.writeHead(200, { 'Content-Type': 'application/json' });
3794 res.end(JSON.stringify({ success: true, employees }));
3795 }
3796 });
3797 }
3798 );
3799 return;
3800 }
3801
3802 database.database.get(
3803 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3804 [personalId, storeId],
3805 (err, ownsStore) => {
3806 if (err || !ownsStore) {
3807 res.writeHead(403, { 'Content-Type': 'application/json' });
3808 res.end(JSON.stringify({ success: false, message: 'You are not authorized to view employees in this store' }));
3809 return;
3810 }
3811
3812 database.getStoreEmployees(storeId, (err, employees) => {
3813 if (err) {
3814 res.writeHead(500, { 'Content-Type': 'application/json' });
3815 res.end(JSON.stringify({ success: false, message: 'Error fetching store employees' }));
3816 } else {
3817 res.writeHead(200, { 'Content-Type': 'application/json' });
3818 res.end(JSON.stringify({ success: true, employees }));
3819 }
3820 });
3821 }
3822 );
3823 });
3824 }
3825
3826 else if (pathname === '/api/store-reports' && req.method === 'GET') {
3827 requireStoreOwner()(req, res, (personalId) => {
3828 const storeId = parsedUrl.query.storeId;
3829
3830 if (!storeId) {
3831 database.database.get(
3832 'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
3833 [personalId],
3834 (err, store) => {
3835 if (err || !store) {
3836 res.writeHead(400, { 'Content-Type': 'application/json' });
3837 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
3838 return;
3839 }
3840
3841 database.getStoreReports(store.store_id, (err, reports) => {
3842 if (err) {
3843 res.writeHead(500, { 'Content-Type': 'application/json' });
3844 res.end(JSON.stringify({ success: false, message: 'Error fetching store reports' }));
3845 } else {
3846 res.writeHead(200, { 'Content-Type': 'application/json' });
3847 res.end(JSON.stringify({ success: true, reports }));
3848 }
3849 });
3850 }
3851 );
3852 return;
3853 }
3854
3855 database.database.get(
3856 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3857 [personalId, storeId],
3858 (err, ownsStore) => {
3859 if (err || !ownsStore) {
3860 res.writeHead(403, { 'Content-Type': 'application/json' });
3861 res.end(JSON.stringify({ success: false, message: 'You are not authorized to view reports in this store' }));
3862 return;
3863 }
3864
3865 database.getStoreReports(storeId, (err, reports) => {
3866 if (err) {
3867 res.writeHead(500, { 'Content-Type': 'application/json' });
3868 res.end(JSON.stringify({ success: false, message: 'Error fetching store reports' }));
3869 } else {
3870 res.writeHead(200, { 'Content-Type': 'application/json' });
3871 res.end(JSON.stringify({ success: true, reports }));
3872 }
3873 });
3874 }
3875 );
3876 });
3877 }
3878
3879 else if (pathname === '/api/store-stats' && req.method === 'GET') {
3880 requireStoreOwner()(req, res, (personalId) => {
3881 const storeId = parsedUrl.query.storeId;
3882
3883 if (!storeId) {
3884 database.database.get(
3885 'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
3886 [personalId],
3887 (err, store) => {
3888 if (err || !store) {
3889 res.writeHead(400, { 'Content-Type': 'application/json' });
3890 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
3891 return;
3892 }
3893
3894 database.getStoreStats(store.store_id, (err, stats) => {
3895 if (err) {
3896 res.writeHead(500, { 'Content-Type': 'application/json' });
3897 res.end(JSON.stringify({ success: false, message: 'Error fetching store statistics' }));
3898 } else {
3899 res.writeHead(200, { 'Content-Type': 'application/json' });
3900 res.end(JSON.stringify({ success: true, stats }));
3901 }
3902 });
3903 }
3904 );
3905 return;
3906 }
3907
3908 database.database.get(
3909 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
3910 [personalId, storeId],
3911 (err, ownsStore) => {
3912 if (err || !ownsStore) {
3913 res.writeHead(403, { 'Content-Type': 'application/json' });
3914 res.end(JSON.stringify({ success: false, message: 'You are not authorized to view statistics in this store' }));
3915 return;
3916 }
3917
3918 database.getStoreStats(storeId, (err, stats) => {
3919 if (err) {
3920 res.writeHead(500, { 'Content-Type': 'application/json' });
3921 res.end(JSON.stringify({ success: false, message: 'Error fetching store statistics' }));
3922 } else {
3923 res.writeHead(200, { 'Content-Type': 'application/json' });
3924 res.end(JSON.stringify({ success: true, stats }));
3925 }
3926 });
3927 }
3928 );
3929 });
3930 }
3931
3932 else if (pathname === '/api/employee-tasks' && req.method === 'GET') {
3933 requireAuth(req, res, (userId) => {
3934 const userIdStr = String(userId);
3935
3936 if (!userIdStr.startsWith('personal_')) {
3937 res.writeHead(403, { 'Content-Type': 'application/json' });
3938 res.end(JSON.stringify({ success: false, message: 'Only store employees can access this endpoint' }));
3939 return;
3940 }
3941
3942 const personalId = userIdStr.replace('personal_', '');
3943 const storeId = parsedUrl.query.storeId;
3944
3945 if (!storeId) {
3946 res.writeHead(400, { 'Content-Type': 'application/json' });
3947 res.end(JSON.stringify({ success: false, message: 'Store ID is required' }));
3948 return;
3949 }
3950
3951 database.getEmployeeTasks(personalId, storeId, (err, tasks) => {
3952 if (err) {
3953 res.writeHead(500, { 'Content-Type': 'application/json' });
3954 res.end(JSON.stringify({ success: false, message: 'Error fetching employee tasks' }));
3955 } else {
3956 res.writeHead(200, { 'Content-Type': 'application/json' });
3957 res.end(JSON.stringify({ success: true, tasks }));
3958 }
3959 });
3960 });
3961 }
3962
3963 else if (pathname === '/api/client-stats' && req.method === 'GET') {
3964 requireAuth(req, res, (userId) => {
3965 const userIdStr = String(userId);
3966
3967 if (!userIdStr.startsWith('client_')) {
3968 res.writeHead(403, { 'Content-Type': 'application/json' });
3969 res.end(JSON.stringify({ success: false, message: 'Only clients can access this endpoint' }));
3970 return;
3971 }
3972
3973 const clientId = parseInt(userIdStr.replace('client_', ''));
3974
3975 database.getClientStats(clientId, (err, stats) => {
3976 if (err) {
3977 res.writeHead(500, { 'Content-Type': 'application/json' });
3978 res.end(JSON.stringify({ success: false, message: 'Error fetching client statistics' }));
3979 } else {
3980 res.writeHead(200, { 'Content-Type': 'application/json' });
3981 res.end(JSON.stringify({ success: true, stats }));
3982 }
3983 });
3984 });
3985 }
3986
3987 else if (pathname === '/api/delete-product' && req.method === 'POST') {
3988 requireStoreOwner()(req, res, (personalId) => {
3989 let body = '';
3990 req.on('data', chunk => {
3991 body += chunk.toString();
3992 });
3993
3994 req.on('end', () => {
3995 const { productCode, storeId } = JSON.parse(body);
3996
3997 if (!productCode || !storeId) {
3998 res.writeHead(400, { 'Content-Type': 'application/json' });
3999 res.end(JSON.stringify({ success: false, message: 'Product code and store ID are required' }));
4000 return;
4001 }
4002
4003 database.database.get(
4004 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
4005 [personalId, storeId],
4006 (err, ownsStore) => {
4007 if (err || !ownsStore) {
4008 res.writeHead(403, { 'Content-Type': 'application/json' });
4009 res.end(JSON.stringify({ success: false, message: 'You are not authorized to delete products from this store' }));
4010 return;
4011 }
4012
4013 database.deleteProduct(productCode, storeId, personalId, (err) => {
4014 if (err) {
4015 console.error('Error deleting product:', err);
4016 res.writeHead(500, { 'Content-Type': 'application/json' });
4017 res.end(JSON.stringify({ success: false, message: 'Error deleting product: ' + err.message }));
4018 } else {
4019 database.logAudit(personalId, 'PRODUCT_DELETED', 'product', productCode, 'Product deleted', ipAddress);
4020 res.writeHead(200, { 'Content-Type': 'application/json' });
4021 res.end(JSON.stringify({ success: true, message: 'Product deleted successfully' }));
4022 }
4023 });
4024 }
4025 );
4026 });
4027 });
4028 }
4029
4030 else if (pathname === '/api/product-by-code' && req.method === 'GET') {
4031 requireAuth(req, res, (userId) => {
4032 const parsedUrl = url.parse(req.url, true);
4033 const productCode = parsedUrl.query.code;
4034
4035 if (!productCode) {
4036 res.writeHead(400, { 'Content-Type': 'application/json' });
4037 res.end(JSON.stringify({ success: false, message: 'Product code is required' }));
4038 return;
4039 }
4040
4041 database.getProductByCode(productCode, (err, product) => {
4042 if (err) {
4043 console.error('Error fetching product:', err);
4044 res.writeHead(500, { 'Content-Type': 'application/json' });
4045 res.end(JSON.stringify({ success: false, message: 'Error fetching product' }));
4046 } else if (!product) {
4047 res.writeHead(404, { 'Content-Type': 'application/json' });
4048 res.end(JSON.stringify({ success: false, message: 'Product not found' }));
4049 } else {
4050 res.writeHead(200, { 'Content-Type': 'application/json' });
4051 res.end(JSON.stringify({ success: true, product }));
4052 }
4053 });
4054 });
4055 }
4056
4057 else if (pathname === '/api/generate-report' && req.method === 'POST') {
4058 requireStoreOwner()(req, res, (personalId) => {
4059 let body = '';
4060 req.on('data', chunk => {
4061 body += chunk.toString();
4062 });
4063
4064 req.on('end', () => {
4065 const { storeId, period, startDate, endDate, type } = JSON.parse(body);
4066
4067 if (!storeId || !period || !startDate || !endDate || !type) {
4068 res.writeHead(400, { 'Content-Type': 'application/json' });
4069 res.end(JSON.stringify({ success: false, message: 'All fields are required' }));
4070 return;
4071 }
4072
4073 database.database.get(
4074 'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
4075 [personalId, storeId],
4076 (err, ownsStore) => {
4077 if (err || !ownsStore) {
4078 res.writeHead(403, { 'Content-Type': 'application/json' });
4079 res.end(JSON.stringify({ success: false, message: 'You are not authorized to generate reports for this store' }));
4080 return;
4081 }
4082
4083 const reportId = 'RPT' + Date.now().toString().slice(-6);
4084
4085 database.database.run(
4086 'INSERT INTO report (id, store_id, period, start_date, end_date, type, generated_by, generated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP)',
4087 [reportId, storeId, period, startDate, endDate, type, personalId],
4088 function(err) {
4089 if (err) {
4090 console.error('Error generating report:', err);
4091 res.writeHead(500, { 'Content-Type': 'application/json' });
4092 res.end(JSON.stringify({ success: false, message: 'Error generating report: ' + err.message }));
4093 } else {
4094 database.logAudit(personalId, 'REPORT_GENERATED', 'report', reportId, `Report generated: ${type} for ${period}`, ipAddress);
4095
4096 res.writeHead(200, { 'Content-Type': 'application/json' });
4097 res.end(JSON.stringify({
4098 success: true,
4099 message: 'Report generated successfully',
4100 reportId: reportId,
4101 report: {
4102 id: reportId,
4103 storeId: storeId,
4104 period: period,
4105 startDate: startDate,
4106 endDate: endDate,
4107 type: type,
4108 generatedBy: personalId,
4109 generatedAt: new Date().toISOString()
4110 }
4111 }));
4112 }
4113 }
4114 );
4115 }
4116 );
4117 });
4118 });
4119 }
4120
4121 else {
4122 res.writeHead(404, { 'Content-Type': 'text/plain' });
4123 res.end('Page not found');
4124 }
4125});
4126
4127server.listen(port, () => {
4128 console.log(`๐ŸŽจ Handcraft Marketplace running at http://localhost:${port}`);
4129 console.log('๐Ÿ‘ฅ Roles: Admin, Store Owner, Store Employee, Registered Client, Unregistered Guest');
4130 console.log('๐ŸŽฏ Features: Product browsing, ordering, reviews, store management');
4131 console.log('๐Ÿช Store Registration: Available at /register-store.html');
4132 console.log('๐Ÿ‘ค Client Registration: Available at /register.html');
4133});
Note: See TracBrowser for help on using the repository browser.