source: server.js@ 69f2a41

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

Initial commit

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