Changes in / [6fea37e:6149556]


Ignore:
Files:
578 added
6 edited

Legend:

Unmodified
Added
Removed
  • .env

    r6fea37e r6149556  
     1# PostgreSQL Connection via SSH Tunnel
     2# SSH tunnel should be established to: 194.149.135.130
     3# SSH username: t_handcraft_store
     4# SSH password: 9f0985a6
     5# After SSH tunnel, connect to localhost:5432
     6
     7PGHOST=localhost
     8PGPORT=9999
     9PGDATABASE=db_202526z_va_prj_handcraft_store
     10PGUSER=db_202526z_va_prj_handcraft_store_owner
     11PGPASSWORD=77769964489a
     12
    113SMTP_HOST=smtp.ethereal.email
    2 SMTP_PORT=587
     14SMTP_PORT=3000
    315SMTP_USER=frederik52@ethereal.email
    416SMTP_PASS=sgqN9t4qn7RxCdavyv
    517JWT_SECRET=handcraft_marketplace_secret_key_2024
    618PORT=3000
    7 DB_PATH=./handcraft.db
  • README.md

    r6fea37e r6149556  
    33 * Open the project folder in Command line
    44 * Run npm install command to install all dependencies
    5  * Run npm start to lunch the program
     5 * Run npm start to lunch the cmdprogram
    66   * After running the command, app is available at locatlhost:3000
    77 * See the database with command node view-database.js
    … …  
    1111
    1212Klimentina Efremova
     13
     14=======
  • database.js

    r6fea37e r6149556  
    1 const sqlite3 = require('sqlite3').verbose();
    2 const path = require('path');
     1const { Pool } = require('pg');
    32const bcrypt = require('bcryptjs');
    4 const crypto = require('crypto');
    5 
    6 const dbPath = path.join(__dirname, 'database', 'handcraft.db');
    7 const dbDir = path.dirname(dbPath);
    8 
    9 // Create database directory if it doesn't exist
    10 const fs = require('fs');
    11 if (!fs.existsSync(dbDir)) {
    12     fs.mkdirSync(dbDir, { recursive: true });
    13 }
    14 
    15 const database = new sqlite3.Database(dbPath, (err) => {
    16     if (err) {
    17         console.error('Error opening database:', err.message);
    18     } else {
    19         console.log('āœ… Connected to SQLite database');
    20     }
     3require('dotenv').config();
     4
     5/*
     6 * ============================================================
     7 * PostgreSQL CONNECTION
     8 * ============================================================
     9 *
     10 * Put your actual FINKI connection information in .env
     11 *
     12 * Example:
     13 *
     14 * PGHOST=localhost
     15 * PGPORT=5432
     16 * PGDATABASE=handcraft
     17 * PGUSER=your_username
     18 * PGPASSWORD=your_password
     19 *
     20 * If you use the SSH tunnel, PGHOST/PGPORT will normally
     21 * point to the LOCAL end of the SSH tunnel.
     22 */
     23
     24const pool = new Pool({
     25    host: process.env.PGHOST || 'localhost',
     26    port: parseInt(process.env.PGPORT || '5432', 10),
     27    database: process.env.PGDATABASE || 'db_202526z_va_prj_handcraft_store',
     28    user: process.env.PGUSER || 'db_202526z_va_prj_handcraft_store_owner',
     29    password: process.env.PGPASSWORD || '',
     30    max: 10,
     31    idleTimeoutMillis: 30000,
     32    connectionTimeoutMillis: 10000
    2133});
    2234
    23 // Enable foreign keys
    24 database.run('PRAGMA foreign_keys = ON');
    25 
    26 // Helper function to ensure general category exists with ID 1
     35pool.on('connect', () => {
     36    console.log('āœ… Connected to PostgreSQL database');
     37});
     38
     39pool.on('error', (err) => {
     40    console.error('āŒ Unexpected PostgreSQL pool error:', err);
     41});
     42
     43/*
     44 * ============================================================
     45 * HELPER
     46 * ============================================================
     47 */
     48
     49function query(text, params = [], callback) {
     50    pool.query(text, params)
     51        .then(result => {
     52            callback(null, result);
     53        })
     54        .catch(err => {
     55            callback(err, null);
     56        });
     57}
     58
     59
     60/*
     61 * ============================================================
     62 * GENERAL CATEGORY
     63 * ============================================================
     64 */
     65
    2766function ensureGeneralCategory(callback) {
    28     // First check if category with ID 1 exists and is named 'General'
    29     database.get('SELECT category_id, name FROM category WHERE category_id = 1', [], (err, row) => {
    30         if (err) {
    31             callback(err);
    32         } else if (row && row.name === 'General') {
    33             // Category with ID 1 already exists and is General
    34             console.log('āœ… General category exists with ID: 1');
    35             callback(null);
    36         } else if (row && row.name !== 'General') {
    37             // Category with ID 1 exists but has different name - update it
    38             database.run(
    39                 'UPDATE category SET name = ?, description = ? WHERE category_id = 1',
    40                 ['General', 'General products category'],
    41                 function(err) {
     67
     68    query(
     69        `SELECT category_id, name
     70         FROM category
     71         WHERE category_id = 1`,
     72        [],
     73        (err, result) => {
     74
     75            if (err) {
     76                callback(err);
     77                return;
     78            }
     79
     80            const row = result.rows[0];
     81
     82            if (row && row.name === 'General') {
     83
     84                console.log('āœ… General category exists with ID: 1');
     85                callback(null);
     86
     87            } else if (row && row.name !== 'General') {
     88
     89                query(
     90                    `UPDATE category
     91                     SET name = $1,
     92                         description = $2
     93                     WHERE category_id = 1`,
     94                    ['General', 'General products category'],
     95                    (err) => {
     96
     97                        if (err) {
     98                            callback(err);
     99                        } else {
     100                            console.log('āœ… Updated category ID 1 to General');
     101                            callback(null);
     102                        }
     103                    }
     104                );
     105
     106            } else {
     107
     108                query(
     109                    `INSERT INTO category
     110                        (category_id, name, description)
     111                     VALUES
     112                        (1, $1, $2)
     113                     ON CONFLICT (category_id) DO NOTHING`,
     114                    ['General', 'General products category'],
     115                    (err) => {
     116
     117                        if (err) {
     118                            callback(err);
     119                        } else {
     120                            console.log('āœ… Created General category with ID: 1');
     121                            callback(null);
     122                        }
     123                    }
     124                );
     125            }
     126        }
     127    );
     128}
     129
     130
     131function getGeneralCategoryId(callback) {
     132
     133    query(
     134        `SELECT category_id
     135         FROM category
     136         WHERE category_id = 1
     137           AND name = $1`,
     138        ['General'],
     139        (err, result) => {
     140
     141            if (err) {
     142                callback(err, null);
     143                return;
     144            }
     145
     146            if (result.rows.length > 0) {
     147                callback(null, result.rows[0].category_id);
     148                return;
     149            }
     150
     151            query(
     152                `SELECT category_id
     153                 FROM category
     154                 WHERE name = $1`,
     155                ['General'],
     156                (err, result) => {
     157
    42158                    if (err) {
    43                         callback(err);
    44                     } else {
    45                         console.log('āœ… Updated category ID 1 to General');
    46                         callback(null);
     159                        callback(err, null);
     160                        return;
    47161                    }
    48                 }
    49             );
    50         } else {
    51             // No category with ID 1 exists, create it
    52             // First, check if we need to reset the autoincrement sequence
    53             database.run(
    54                 'INSERT INTO category (category_id, name, description) VALUES (1, ?, ?)',
    55                 ['General', 'General products category'],
    56                 function(err) {
    57                     if (err) {
    58                         // If insert fails, try without specifying ID (let SQLite assign it)
    59                         database.run(
    60                             'INSERT INTO category (name, description) VALUES (?, ?)',
    61                             ['General', 'General products category'],
    62                             function(err) {
    63                                 if (err) {
    64                                     callback(err);
    65                                 } else {
    66                                     console.log('āœ… Created General category with auto-assigned ID');
    67                                     callback(null);
    68                                 }
    69                             }
    70                         );
    71                     } else {
    72                         console.log('āœ… Created General category with ID: 1');
    73                         callback(null);
     162
     163                    if (result.rows.length > 0) {
     164                        callback(null, result.rows[0].category_id);
     165                        return;
    74166                    }
    75                 }
    76             );
    77         }
    78     });
    79 }
    80 
    81 // Helper function to get the General category ID
    82 function getGeneralCategoryId(callback) {
    83     // First try to get category with ID 1 that is named 'General'
    84     database.get('SELECT category_id FROM category WHERE category_id = 1 AND name = ?', ['General'], (err, row) => {
    85         if (err) {
    86             callback(err, null);
    87         } else if (row) {
    88             callback(null, row.category_id);
    89         } else {
    90             // If not found with ID 1, try to find by name
    91             database.get('SELECT category_id FROM category WHERE name = ?', ['General'], (err, row) => {
    92                 if (err) {
    93                     callback(err, null);
    94                 } else if (row) {
    95                     callback(null, row.category_id);
    96                 } else {
    97                     // Create General category if it doesn't exist
    98                     database.run(
    99                         'INSERT INTO category (name, description) VALUES (?, ?)',
     167
     168                    query(
     169                        `INSERT INTO category
     170                            (name, description)
     171                         VALUES
     172                            ($1, $2)
     173                         RETURNING category_id`,
    100174                        ['General', 'General products category'],
    101                         function(err) {
     175                        (err, result) => {
     176
    102177                            if (err) {
    103178                                callback(err, null);
    104179                            } else {
    105                                 const newId = this.lastID;
    106                                 console.log(`āœ… Created new General category with ID: ${newId}`);
     180                                const newId = result.rows[0].category_id;
     181
     182                                console.log(
     183                                    `āœ… Created new General category with ID: ${newId}`
     184                                );
     185
    107186                                callback(null, newId);
    108187                            }
    … …  
    110189                    );
    111190                }
    112             });
    113         }
    114     });
    115 }
    116 
    117 // User functions
     191            );
     192        }
     193    );
     194}
     195
     196
     197/*
     198 * ============================================================
     199 * USER FUNCTIONS
     200 * ============================================================
     201 */
     202
    118203function getUserByUsername(username, callback) {
    119     database.get('SELECT * FROM users WHERE username = ?', [username], (err, row) => {
    120         callback(err, row);
    121     });
    122 }
     204
     205    query(
     206        `SELECT *
     207         FROM users
     208         WHERE username = $1`,
     209        [username],
     210        (err, result) => {
     211
     212            callback(
     213                err,
     214                result ? result.rows[0] : null
     215            );
     216        }
     217    );
     218}
     219
    123220
    124221function getUserById(id, callback) {
    125     database.get('SELECT * FROM users WHERE id = ?', [id], (err, row) => {
    126         if (err || !row) {
    127             callback(err, null);
    128             return;
    129         }
    130 
    131         // Get user roles
    132         database.all(
    133             `SELECT r.* FROM roles r
    134        JOIN user_roles ur ON r.role_id = ur.role_id
    135        WHERE ur.user_id = ?`,
    136             [id],
    137             (err, roles) => {
    138                 if (err) {
    139                     callback(err, null);
    140                 } else {
    141                     row.roles = roles || [];
    142                     callback(null, row);
     222
     223    query(
     224        `SELECT *
     225         FROM users
     226         WHERE id = $1`,
     227        [id],
     228        (err, result) => {
     229
     230            if (err || !result || result.rows.length === 0) {
     231                callback(err, null);
     232                return;
     233            }
     234
     235            const row = result.rows[0];
     236
     237            query(
     238                `SELECT r.*
     239                 FROM roles r
     240                 JOIN user_roles ur
     241                   ON r.role_id = ur.role_id
     242                 WHERE ur.user_id = $1`,
     243                [id],
     244                (err, result) => {
     245
     246                    if (err) {
     247                        callback(err, null);
     248                    } else {
     249                        row.roles = result.rows || [];
     250                        callback(null, row);
     251                    }
    143252                }
    144             }
    145         );
    146     });
    147 }
     253            );
     254        }
     255    );
     256}
     257
    148258
    149259function createUser(id, username, email, password, userType, callback) {
     260
    150261    const hashedPassword = bcrypt.hashSync(password, 10);
    151     database.run(
    152         'INSERT INTO users (id, username, email, password, user_type) VALUES (?, ?, ?, ?, ?)',
     262
     263    query(
     264        `INSERT INTO users
     265            (id, username, email, password, user_type)
     266         VALUES
     267            ($1, $2, $3, $4, $5)`,
    153268        [id, username, email, hashedPassword, userType],
    154         function(err) {
     269        (err) => {
     270
    155271            if (err) {
    156272                callback(err, null);
    … …  
    162278}
    163279
    164 // Client functions
     280
     281/*
     282 * ============================================================
     283 * CLIENT FUNCTIONS
     284 * ============================================================
     285 */
     286
    165287function getClientByEmail(email, callback) {
    166     database.get('SELECT * FROM client WHERE email = ?', [email], (err, row) => {
    167         callback(err, row);
    168     });
    169 }
     288
     289    query(
     290        `SELECT *
     291         FROM client
     292         WHERE email = $1`,
     293        [email],
     294        (err, result) => {
     295
     296            callback(
     297                err,
     298                result ? result.rows[0] : null
     299            );
     300        }
     301    );
     302}
     303
    170304
    171305function getClientById(id, callback) {
    172     database.get('SELECT * FROM client WHERE client_id = ?', [id], (err, row) => {
    173         callback(err, row);
    174     });
    175 }
     306
     307    query(
     308        `SELECT *
     309         FROM client
     310         WHERE client_id = $1`,
     311        [id],
     312        (err, result) => {
     313
     314            callback(
     315                err,
     316                result ? result.rows[0] : null
     317            );
     318        }
     319    );
     320}
     321
    176322
    177323function createClient(clientData, callback) {
    178     const hashedPassword = bcrypt.hashSync(clientData.password, 10);
    179     database.run(
    180         'INSERT INTO client (first_name, last_name, email, password) VALUES (?, ?, ?, ?)',
    181         [clientData.first_name, clientData.last_name, clientData.email, hashedPassword],
    182         function(err) {
     324
     325    const hashedPassword =
     326        bcrypt.hashSync(clientData.password, 10);
     327
     328    query(
     329        `INSERT INTO client
     330            (first_name, last_name, email, password)
     331         VALUES
     332            ($1, $2, $3, $4)
     333         RETURNING client_id`,
     334        [
     335            clientData.first_name,
     336            clientData.last_name,
     337            clientData.email,
     338            hashedPassword
     339        ],
     340        (err, result) => {
     341
    183342            if (err) {
    184343                callback(err, null);
    185344            } else {
    186                 callback(null, this.lastID);
    187             }
    188         }
    189     );
    190 }
    191 
    192 function verifyClientPassword(password, hashedPassword, callback) {
     345                callback(
     346                    null,
     347                    result.rows[0].client_id
     348                );
     349            }
     350        }
     351    );
     352}
     353
     354
     355function verifyClientPassword(
     356    password,
     357    hashedPassword,
     358    callback
     359) {
     360
    193361    try {
    194         const isValid = bcrypt.compareSync(password, hashedPassword);
     362
     363        const isValid =
     364            bcrypt.compareSync(password, hashedPassword);
     365
    195366        callback(null, isValid);
     367
    196368    } catch (err) {
     369
    197370        callback(err, false);
    198371    }
    199372}
    200373
    201 // Personal functions
     374
     375/*
     376 * ============================================================
     377 * PERSONAL / EMPLOYEE FUNCTIONS
     378 * ============================================================
     379 */
     380
    202381function getPersonalByEmail(email, callback) {
    203     database.get('SELECT * FROM personal WHERE email = ?', [email], (err, row) => {
    204         callback(err, row);
    205     });
    206 }
     382
     383    query(
     384        `SELECT *
     385         FROM personal
     386         WHERE email = $1`,
     387        [email],
     388        (err, result) => {
     389
     390            callback(
     391                err,
     392                result ? result.rows[0] : null
     393            );
     394        }
     395    );
     396}
     397
    207398
    208399function getPersonalById(id, callback) {
    209     database.get('SELECT * FROM personal WHERE id = ?', [id], (err, row) => {
    210         callback(err, row);
    211     });
    212 }
    213 
    214 // Password verification for regular users
     400
     401    query(
     402        `SELECT *
     403         FROM personal
     404         WHERE id = $1`,
     405        [id],
     406        (err, result) => {
     407
     408            callback(
     409                err,
     410                result ? result.rows[0] : null
     411            );
     412        }
     413    );
     414}
     415
     416
    215417function verifyPassword(password, hashedPassword) {
    216418    return bcrypt.compareSync(password, hashedPassword);
    217419}
    218420
    219 // Password update
    220 function updatePasswordAndClearForce(userId, newPassword, callback) {
    221     const hashedPassword = bcrypt.hashSync(newPassword, 10);
    222     database.run(
    223         'UPDATE users SET password = ?, force_password_change = 0 WHERE id = ?',
     421
     422function updatePasswordAndClearForce(
     423    userId,
     424    newPassword,
     425    callback
     426) {
     427
     428    const hashedPassword =
     429        bcrypt.hashSync(newPassword, 10);
     430
     431    query(
     432        `UPDATE users
     433         SET password = $1,
     434             force_password_change = 0
     435         WHERE id = $2`,
    224436        [hashedPassword, userId],
    225         function(err) {
     437        (err) => {
     438
    226439            callback(err);
    227440        }
    … …  
    229442}
    230443
    231 // Product functions
     444
     445/*
     446 * ============================================================
     447 * PRODUCT FUNCTIONS
     448 * ============================================================
     449 */
     450
    232451function getProducts(categoryId, searchTerm, callback) {
    233     let query = `
    234     SELECT p.*, c.name as category_name, s.name as store_name
    235     FROM product p
    236     JOIN category c ON p.category_id = c.category_id
    237     JOIN store s ON p.store_id = s.store_id
    238     WHERE 1=1
    239   `;
     452
     453    let queryText = `
     454        SELECT
     455            p.*,
     456            c.name AS category_name,
     457            s.name AS store_name
     458        FROM product p
     459        JOIN category c
     460          ON p.category_id = c.category_id
     461        JOIN store s
     462          ON p.store_id = s.store_id
     463        WHERE 1 = 1
     464    `;
     465
    240466    const params = [];
     467    let paramIndex = 1;
    241468
    242469    if (categoryId && categoryId !== 'all') {
    243         query += ' AND p.category_id = ?';
     470
     471        queryText +=
     472            ` AND p.category_id = $${paramIndex}`;
     473
    244474        params.push(categoryId);
     475        paramIndex++;
    245476    }
    246477
    247478    if (searchTerm) {
    248         query += ' AND (p.description LIKE ? OR p.code LIKE ?)';
    249         params.push(`%${searchTerm}%`, `%${searchTerm}%`);
     479
     480        queryText +=
     481            ` AND (
     482                p.description ILIKE $${paramIndex}
     483                OR p.code ILIKE $${paramIndex + 1}
     484            )`;
     485
     486        params.push(`%${searchTerm}%`);
     487        params.push(`%${searchTerm}%`);
     488
     489        paramIndex += 2;
    250490    }
    251491
    252     database.all(query, params, (err, rows) => {
    253         if (err) {
    254             callback(err, null);
    255         } else {
    256             callback(null, rows || []);
    257         }
    258     });
    259 }
    260 
    261 function getProductById(id, callback) {
    262     database.get(
    263         `SELECT p.*, c.name as category_name, s.name as store_name
    264      FROM product p
    265      JOIN category c ON p.category_id = c.category_id
    266      JOIN store s ON p.store_id = s.store_id
    267      WHERE p.id = ?`,
    268         [id],
    269         (err, row) => {
    270             if (err || !row) {
     492    queryText += ` ORDER BY p.code`;
     493
     494    query(
     495        queryText,
     496        params,
     497        (err, result) => {
     498
     499            if (err) {
    271500                callback(err, null);
    272501            } else {
    273                 // Get images for product
    274                 database.all(
    275                     'SELECT * FROM image WHERE product_code = ?',
    276                     [row.code],
    277                     (err, images) => {
    278                         if (err) {
    279                             callback(err, null);
    280                         } else {
    281                             row.images = images || [];
    282                             // Get colors for product
    283                             database.all(
    284                                 'SELECT * FROM color WHERE product_code = ?',
    285                                 [row.code],
    286                                 (err, colors) => {
    287                                     if (err) {
    288                                         callback(err, null);
    289                                     } else {
    290                                         row.colors = colors || [];
    291                                         callback(null, row);
    292                                     }
    293                                 }
    294                             );
     502                callback(null, result.rows || []);
     503            }
     504        }
     505    );
     506}
     507
     508
     509function getProductById(id, callback) {
     510
     511    query(
     512        `SELECT
     513            p.*,
     514            c.name AS category_name,
     515            s.name AS store_name
     516         FROM product p
     517         JOIN category c
     518           ON p.category_id = c.category_id
     519         JOIN store s
     520           ON p.store_id = s.store_id
     521         WHERE p.id = $1`,
     522        [id],
     523        (err, result) => {
     524
     525            if (err || result.rows.length === 0) {
     526                callback(err, null);
     527                return;
     528            }
     529
     530            const row = result.rows[0];
     531
     532            query(
     533                `SELECT *
     534                 FROM image
     535                 WHERE product_code = $1`,
     536                [row.code],
     537                (err, result) => {
     538
     539                    if (err) {
     540                        callback(err, null);
     541                        return;
     542                    }
     543
     544                    row.images = result.rows || [];
     545
     546                    query(
     547                        `SELECT *
     548                         FROM color
     549                         WHERE product_code = $1`,
     550                        [row.code],
     551                        (err, result) => {
     552
     553                            if (err) {
     554                                callback(err, null);
     555                            } else {
     556                                row.colors =
     557                                    result.rows || [];
     558
     559                                callback(null, row);
     560                            }
    295561                        }
     562                    );
     563                }
     564            );
     565        }
     566    );
     567}
     568
     569
     570function getProductByCode(code, callback) {
     571
     572    query(
     573        `SELECT
     574            p.*,
     575            c.name AS category_name,
     576            s.name AS store_name
     577         FROM product p
     578         JOIN category c
     579           ON p.category_id = c.category_id
     580         JOIN store s
     581           ON p.store_id = s.store_id
     582         WHERE p.code = $1`,
     583        [code],
     584        (err, result) => {
     585
     586            if (err || result.rows.length === 0) {
     587                callback(err, null);
     588                return;
     589            }
     590
     591            const row = result.rows[0];
     592
     593            query(
     594                `SELECT *
     595                 FROM image
     596                 WHERE product_code = $1`,
     597                [code],
     598                (err, result) => {
     599
     600                    if (err) {
     601                        callback(err, null);
     602                        return;
    296603                    }
    297                 );
    298             }
    299         }
    300     );
    301 }
    302 
    303 function getProductByCode(code, callback) {
    304     database.get(
    305         `SELECT p.*, c.name as category_name, s.name as store_name
    306      FROM product p
    307      JOIN category c ON p.category_id = c.category_id
    308      JOIN store s ON p.store_id = s.store_id
    309      WHERE p.code = ?`,
    310         [code],
    311         (err, row) => {
    312             if (err || !row) {
    313                 callback(err, null);
    314             } else {
    315                 // Get images for product
    316                 database.all(
    317                     'SELECT * FROM image WHERE product_code = ?',
    318                     [code],
    319                     (err, images) => {
    320                         if (err) {
    321                             callback(err, null);
    322                         } else {
    323                             row.images = images || [];
    324                             // Get colors for product
    325                             database.all(
    326                                 'SELECT * FROM color WHERE product_code = ?',
    327                                 [code],
    328                                 (err, colors) => {
    329                                     if (err) {
    330                                         callback(err, null);
    331                                     } else {
    332                                         row.colors = colors || [];
    333                                         callback(null, row);
    334                                     }
    335                                 }
    336                             );
     604
     605                    row.images = result.rows || [];
     606
     607                    query(
     608                        `SELECT *
     609                         FROM color
     610                         WHERE product_code = $1`,
     611                        [code],
     612                        (err, result) => {
     613
     614                            if (err) {
     615                                callback(err, null);
     616                            } else {
     617
     618                                row.colors =
     619                                    result.rows || [];
     620
     621                                callback(null, row);
     622                            }
    337623                        }
    338                     }
    339                 );
    340             }
    341         }
    342     );
    343 }
     624                    );
     625                }
     626            );
     627        }
     628    );
     629}
     630
    344631
    345632function addProduct(personalId, productData, callback) {
     633
    346634    getGeneralCategoryId((err, generalCategoryId) => {
     635
    347636        if (err) {
    348637            callback(err, null);
    … …  
    350639        }
    351640
    352         const categoryId = productData.category_id || generalCategoryId;
    353 
    354         // FIXED: Added validation for required fields
     641        const categoryId =
     642            productData.category_id || generalCategoryId;
     643
    355644        if (!productData.code) {
    356             callback(new Error('Product code is required'), null);
     645            callback(
     646                new Error('Product code is required'),
     647                null
     648            );
    357649            return;
    358650        }
    359651
    360652        if (!productData.store_id) {
    361             callback(new Error('Store ID is required'), null);
     653            callback(
     654                new Error('Store ID is required'),
     655                null
     656            );
    362657            return;
    363658        }
    364659
    365         database.run(
    366             `INSERT INTO product (
    367         id, code, description, price, availability, weight, dimensions,
    368         production_time, category_id, store_id, created_at
    369       ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
     660        const productId =
     661            'PROD_' +
     662            Date.now().toString().slice(-8);
     663
     664        query(
     665            `INSERT INTO product
     666                (
     667                    id,
     668                    code,
     669                    description,
     670                    price,
     671                    availability,
     672                    weight,
     673                    dimensions,
     674                    production_time,
     675                    category_id,
     676                    store_id,
     677                    created_at
     678                )
     679             VALUES
     680                (
     681                    $1,
     682                    $2,
     683                    $3,
     684                    $4,
     685                    $5,
     686                    $6,
     687                    $7,
     688                    $8,
     689                    $9,
     690                    $10,
     691                    NOW()
     692                )
     693             RETURNING id`,
    370694            [
    371                 'PROD_' + Date.now().toString().slice(-8), // Generate a unique ID
     695                productId,
    372696                productData.code,
    373697                productData.description || 'No description',
    … …  
    380704                productData.store_id
    381705            ],
    382             function(err) {
     706            (err, result) => {
     707
    383708                if (err) {
    384709                    callback(err, null);
    385                 } else {
    386                     const productId = this.lastID;
    387 
    388                     // Log the change
    389                     database.run(
    390                         `INSERT INTO "change" (date_and_time, product_code, changes)
    391              VALUES (datetime('now'), ?, ?)`,
    392                         [productData.code, 'Product created'],
    393                         function(err) {
    394                             if (err) {
    395                                 console.error('Error logging product creation:', err);
    396                             }
     710                    return;
     711                }
     712
     713                const returnedId = result.rows[0].id;
     714
     715                /*
     716                 * Log product creation
     717                 */
     718                query(
     719                    `INSERT INTO "change"
     720                        (date_and_time, product_code, changes)
     721                     VALUES
     722                        (NOW(), $1, $2)`,
     723                    [
     724                        productData.code,
     725                        'Product created'
     726                    ],
     727                    (err) => {
     728
     729                        if (err) {
     730                            console.error(
     731                                'Error logging product creation:',
     732                                err
     733                            );
     734                        }
     735                    }
     736                );
     737
     738                /*
     739                 * Log who made the change
     740                 */
     741                query(
     742                    `INSERT INTO makes_change
     743                        (
     744                            personal_id,
     745                            change_date_time,
     746                            product_code
     747                        )
     748                     VALUES
     749                        ($1, NOW(), $2)`,
     750                    [
     751                        personalId,
     752                        productData.code
     753                    ],
     754                    (err) => {
     755
     756                        if (err) {
     757                            console.error(
     758                                'Error logging change maker:',
     759                                err
     760                            );
     761                        }
     762                    }
     763                );
     764
     765                /*
     766                 * Insert images
     767                 */
     768                if (
     769                    productData.images &&
     770                    Array.isArray(productData.images) &&
     771                    productData.images.length > 0
     772                ) {
     773
     774                    productData.images.forEach(
     775                        (imageUrl, index) => {
     776
     777                            query(
     778                                `INSERT INTO image
     779                                    (
     780                                        product_code,
     781                                        image_url,
     782                                        is_primary
     783                                    )
     784                                 VALUES
     785                                    ($1, $2, $3)`,
     786                                [
     787                                    productData.code,
     788                                    imageUrl,
     789                                    index === 0
     790                                ],
     791                                (err) => {
     792
     793                                    if (err) {
     794                                        console.error(
     795                                            'Error inserting image:',
     796                                            err
     797                                        );
     798                                    }
     799                                }
     800                            );
    397801                        }
    398802                    );
    399 
    400                     // Log who made the change
    401                     database.run(
    402                         `INSERT INTO makes_change (personal_id, change_date_time, product_code)
    403              VALUES (?, datetime('now'), ?)`,
    404                         [personalId, productData.code],
    405                         function(err) {
    406                             if (err) {
    407                                 console.error('Error logging change maker:', err);
     803                }
     804
     805                /*
     806                 * Insert colors
     807                 */
     808                if (
     809                    productData.colors &&
     810                    Array.isArray(productData.colors) &&
     811                    productData.colors.length > 0
     812                ) {
     813
     814                    productData.colors.forEach(color => {
     815
     816                        query(
     817                            `INSERT INTO color
     818                                (product_code, name)
     819                             VALUES
     820                                ($1, $2)`,
     821                            [
     822                                productData.code,
     823                                color
     824                            ],
     825                            (err) => {
     826
     827                                if (err) {
     828                                    console.error(
     829                                        'Error inserting color:',
     830                                        err
     831                                    );
     832                                }
    408833                            }
     834                        );
     835                    });
     836                }
     837
     838                callback(null, returnedId);
     839            }
     840        );
     841    });
     842}
     843
     844
     845function updateProduct(
     846    personalId,
     847    productData,
     848    callback
     849) {
     850
     851    const updates = [];
     852    const params = [];
     853
     854    if (productData.description !== undefined) {
     855        updates.push(`description = $${params.length + 1}`);
     856        params.push(productData.description);
     857    }
     858
     859    if (productData.price !== undefined) {
     860        updates.push(`price = $${params.length + 1}`);
     861        params.push(productData.price);
     862    }
     863
     864    if (productData.availability !== undefined) {
     865        updates.push(`availability = $${params.length + 1}`);
     866        params.push(productData.availability);
     867    }
     868
     869    if (productData.weight !== undefined) {
     870        updates.push(`weight = $${params.length + 1}`);
     871        params.push(productData.weight);
     872    }
     873
     874    if (productData.dimensions !== undefined) {
     875        updates.push(`dimensions = $${params.length + 1}`);
     876        params.push(productData.dimensions);
     877    }
     878
     879    if (productData.production_time !== undefined) {
     880        updates.push(`production_time = $${params.length + 1}`);
     881        params.push(productData.production_time);
     882    }
     883
     884    if (productData.category_id !== undefined) {
     885        updates.push(`category_id = $${params.length + 1}`);
     886        params.push(productData.category_id);
     887    }
     888
     889    if (updates.length === 0) {
     890        callback(null, 0);
     891        return;
     892    }
     893
     894    params.push(productData.code);
     895
     896    const codeParameter = params.length;
     897
     898    query(
     899        `UPDATE product
     900         SET ${updates.join(', ')}
     901         WHERE code = $${codeParameter}`,
     902        params,
     903        (err, result) => {
     904
     905            if (err) {
     906                callback(err, null);
     907                return;
     908            }
     909
     910            const changesDesc =
     911                `Product updated: ${updates.join(', ')}`;
     912
     913            /*
     914             * Log product change
     915             */
     916            query(
     917                `INSERT INTO "change"
     918                    (
     919                        date_and_time,
     920                        product_code,
     921                        changes
     922                    )
     923                 VALUES
     924                    (NOW(), $1, $2)`,
     925                [
     926                    productData.code,
     927                    changesDesc
     928                ],
     929                (err) => {
     930
     931                    if (err) {
     932                        console.error(
     933                            'Error logging product update:',
     934                            err
     935                        );
     936                    }
     937                }
     938            );
     939
     940            /*
     941             * Log who made the change
     942             */
     943            query(
     944                `INSERT INTO makes_change
     945                    (
     946                        personal_id,
     947                        change_date_time,
     948                        product_code
     949                    )
     950                 VALUES
     951                    ($1, NOW(), $2)`,
     952                [
     953                    personalId,
     954                    productData.code
     955                ],
     956                (err) => {
     957
     958                    if (err) {
     959                        console.error(
     960                            'Error logging change maker:',
     961                            err
     962                        );
     963                    }
     964                }
     965            );
     966
     967            /*
     968             * Images
     969             */
     970            if (
     971                productData.images &&
     972                Array.isArray(productData.images)
     973            ) {
     974
     975                query(
     976                    `DELETE FROM image
     977                     WHERE product_code = $1`,
     978                    [productData.code],
     979                    (err) => {
     980
     981                        if (err) {
     982                            console.error(
     983                                'Error deleting old images:',
     984                                err
     985                            );
     986                            return;
    409987                        }
    410                     );
    411 
    412                     // Insert images if provided
    413                     if (productData.images && Array.isArray(productData.images) && productData.images.length > 0) {
    414                         productData.images.forEach((imageUrl, index) => {
    415                             database.run(
    416                                 `INSERT INTO image (product_code, image_url, is_primary)
    417                  VALUES (?, ?, ?)`,
    418                                 [productData.code, imageUrl, index === 0 ? 1 : 0],
    419                                 function(err) {
     988
     989                        productData.images.forEach(
     990                            (image, index) => {
     991
     992                                query(
     993                                    `INSERT INTO image
     994                                        (
     995                                            product_code,
     996                                            image_url,
     997                                            is_primary
     998                                        )
     999                                     VALUES
     1000                                        ($1, $2, $3)`,
     1001                                    [
     1002                                        productData.code,
     1003                                        image,
     1004                                        index === 0
     1005                                    ],
     1006                                    (err) => {
     1007
     1008                                        if (err) {
     1009                                            console.error(
     1010                                                'Error inserting image:',
     1011                                                err
     1012                                            );
     1013                                        }
     1014                                    }
     1015                                );
     1016                            }
     1017                        );
     1018                    }
     1019                );
     1020            }
     1021
     1022            /*
     1023             * Colors
     1024             */
     1025            if (
     1026                productData.colors &&
     1027                Array.isArray(productData.colors)
     1028            ) {
     1029
     1030                query(
     1031                    `DELETE FROM color
     1032                     WHERE product_code = $1`,
     1033                    [productData.code],
     1034                    (err) => {
     1035
     1036                        if (err) {
     1037                            console.error(
     1038                                'Error deleting old colors:',
     1039                                err
     1040                            );
     1041                            return;
     1042                        }
     1043
     1044                        productData.colors.forEach(color => {
     1045
     1046                            query(
     1047                                `INSERT INTO color
     1048                                    (product_code, name)
     1049                                 VALUES
     1050                                    ($1, $2)`,
     1051                                [
     1052                                    productData.code,
     1053                                    color
     1054                                ],
     1055                                (err) => {
     1056
    4201057                                    if (err) {
    421                                         console.error('Error inserting image:', err);
     1058                                        console.error(
     1059                                            'Error inserting color:',
     1060                                            err
     1061                                        );
    4221062                                    }
    4231063                                }
    … …  
    4251065                        });
    4261066                    }
    427 
    428                     // Insert colors if provided
    429                     if (productData.colors && Array.isArray(productData.colors) && productData.colors.length > 0) {
    430                         productData.colors.forEach(color => {
    431                             database.run(
    432                                 `INSERT INTO color (product_code, name)
    433                                  VALUES (?, ?)`,
    434                                 [productData.code, color],
    435                                 function(err) {
    436                                     if (err) {
    437                                         console.error('Error inserting color:', err);
    438                                     }
    439                                 }
     1067                );
     1068            }
     1069
     1070            callback(null, result.rowCount);
     1071        }
     1072    );
     1073}
     1074
     1075
     1076function deleteProduct(
     1077    productCode,
     1078    storeId,
     1079    personalId,
     1080    callback
     1081) {
     1082
     1083    pool.connect()
     1084        .then(client => {
     1085
     1086            return client.query('BEGIN')
     1087                .then(() => {
     1088
     1089                    return client.query(
     1090                        `INSERT INTO "change"
     1091                            (
     1092                                date_and_time,
     1093                                product_code,
     1094                                changes
     1095                            )
     1096                         VALUES
     1097                            (NOW(), $1, $2)`,
     1098                        [
     1099                            productCode,
     1100                            'Product deleted'
     1101                        ]
     1102                    );
     1103                })
     1104                .then(() => {
     1105
     1106                    return client.query(
     1107                        `INSERT INTO makes_change
     1108                            (
     1109                                personal_id,
     1110                                change_date_time,
     1111                                product_code
     1112                            )
     1113                         VALUES
     1114                            ($1, NOW(), $2)`,
     1115                        [
     1116                            personalId,
     1117                            productCode
     1118                        ]
     1119                    );
     1120                })
     1121                .then(() => {
     1122
     1123                    return client.query(
     1124                        `DELETE FROM product
     1125                         WHERE code = $1
     1126                           AND store_id = $2`,
     1127                        [
     1128                            productCode,
     1129                            storeId
     1130                        ]
     1131                    );
     1132                })
     1133                .then(result => {
     1134
     1135                    return client.query('COMMIT')
     1136                        .then(() => {
     1137
     1138                            client.release();
     1139
     1140                            callback(
     1141                                null,
     1142                                result.rowCount
    4401143                            );
    4411144                        });
    442                     }
    443 
    444                     callback(null, productId);
    445                 }
    446             }
    447         );
    448     });
    449 }
    450 
    451 function updateProduct(personalId, productData, callback) {
    452     const updates = [];
    453     const params = [];
    454 
    455     if (productData.description !== undefined) {
    456         updates.push('description = ?');
    457         params.push(productData.description);
    458     }
    459 
    460     if (productData.price !== undefined) {
    461         updates.push('price = ?');
    462         params.push(productData.price);
    463     }
    464 
    465     if (productData.availability !== undefined) {
    466         updates.push('availability = ?');
    467         params.push(productData.availability);
    468     }
    469 
    470     if (productData.weight !== undefined) {
    471         updates.push('weight = ?');
    472         params.push(productData.weight);
    473     }
    474 
    475     if (productData.dimensions !== undefined) {
    476         updates.push('dimensions = ?');
    477         params.push(productData.dimensions);
    478     }
    479 
    480     if (productData.production_time !== undefined) {
    481         updates.push('production_time = ?');
    482         params.push(productData.production_time);
    483     }
    484 
    485     if (productData.category_id !== undefined) {
    486         updates.push('category_id = ?');
    487         params.push(productData.category_id);
    488     }
    489 
    490     if (updates.length === 0) {
    491         callback(null, 0);
    492         return;
    493     }
    494 
    495     params.push(productData.code);
    496 
    497     database.run(
    498         `UPDATE product SET ${updates.join(', ')} WHERE code = ?`,
    499         params,
    500         function(err) {
     1145                })
     1146                .catch(err => {
     1147
     1148                    return client.query('ROLLBACK')
     1149                        .catch(() => {})
     1150                        .then(() => {
     1151
     1152                            client.release();
     1153                            callback(err);
     1154                        });
     1155                });
     1156        })
     1157        .catch(err => {
     1158            callback(err);
     1159        });
     1160}
     1161
     1162
     1163/*
     1164 * ============================================================
     1165 * CATEGORY FUNCTIONS
     1166 * ============================================================
     1167 */
     1168
     1169function getCategories(callback) {
     1170
     1171    query(
     1172        `SELECT *
     1173         FROM category
     1174         ORDER BY name`,
     1175        [],
     1176        (err, result) => {
     1177
     1178            callback(
     1179                err,
     1180                result ? result.rows : []
     1181            );
     1182        }
     1183    );
     1184}
     1185
     1186
     1187function getCategoriesWithParents(callback) {
     1188
     1189    query(
     1190        `SELECT
     1191            c1.*,
     1192            c2.name AS parent_name
     1193         FROM category c1
     1194         LEFT JOIN category c2
     1195           ON c1.parent_category_id =
     1196              c2.category_id
     1197         ORDER BY c1.name`,
     1198        [],
     1199        (err, result) => {
     1200
     1201            callback(
     1202                err,
     1203                result ? result.rows : []
     1204            );
     1205        }
     1206    );
     1207}
     1208
     1209
     1210function createCategory(categoryData, callback) {
     1211
     1212    query(
     1213        `INSERT INTO category
     1214            (
     1215                name,
     1216                description,
     1217                parent_category_id
     1218            )
     1219         VALUES
     1220            ($1, $2, $3)
     1221         RETURNING category_id`,
     1222        [
     1223            categoryData.name,
     1224            categoryData.description || null,
     1225            categoryData.parent_id || null
     1226        ],
     1227        (err, result) => {
     1228
    5011229            if (err) {
    5021230                callback(err, null);
    5031231            } else {
    504                 // Log the change
    505                 const changesDesc = `Product updated: ${updates.join(', ')}`;
    506                 database.run(
    507                     `INSERT INTO "change" (date_and_time, product_code, changes)
    508            VALUES (datetime('now'), ?, ?)`,
    509                     [productData.code, changesDesc],
    510                     function(err) {
    511                         if (err) {
    512                             console.error('Error logging product update:', err);
    513                         }
    514                         // Log who made the change
    515                         database.run(
    516                             `INSERT INTO makes_change (personal_id, change_date_time, product_code)
    517                VALUES (?, datetime('now'), ?)`,
    518                             [personalId, productData.code],
    519                             function(err) {
    520                                 if (err) {
    521                                     console.error('Error logging change maker:', err);
    522                                 }
    523                             }
    524                         );
    525                     }
    526                 );
    527 
    528                 // Handle images if provided
    529                 if (productData.images && Array.isArray(productData.images)) {
    530                     // Delete old images first
    531                     database.run('DELETE FROM image WHERE product_code = ?', [productData.code], (err) => {
    532                         if (!err) {
    533                             // Insert new images
    534                             productData.images.forEach(image => {
    535                                 database.run(
    536                                     'INSERT INTO image (product_code, image) VALUES (?, ?)',
    537                                     [productData.code, image]
    538                                 );
    539                             });
    540                         }
    541                     });
    542                 }
    543 
    544                 // Handle colors if provided
    545                 if (productData.colors && Array.isArray(productData.colors)) {
    546                     // Delete old colors first
    547                     database.run('DELETE FROM color WHERE product_code = ?', [productData.code], (err) => {
    548                         if (!err) {
    549                             // Insert new colors
    550                             productData.colors.forEach(color => {
    551                                 database.run(
    552                                     'INSERT INTO color (product_code, color) VALUES (?, ?)',
    553                                     [productData.code, color]
    554                                 );
    555                             });
    556                         }
    557                     });
    558                 }
    559 
    560                 callback(null, this.changes);
    561             }
    562         }
    563     );
    564 }
    565 
    566 function deleteProduct(productCode, storeId, personalId, callback) {
    567     database.run('BEGIN TRANSACTION', (err) => {
    568         if (err) {
    569             callback(err);
    570             return;
    571         }
    572 
    573         // Log the deletion
    574         database.run(
    575             `INSERT INTO "change" (date_and_time, product_code, changes)
    576        VALUES (datetime('now'), ?, ?)`,
    577             [productCode, 'Product deleted'],
    578             function(err) {
    579                 if (err) {
    580                     database.run('ROLLBACK');
    581                     callback(err);
    582                     return;
    583                 }
    584 
    585                 // Log who deleted it
    586                 database.run(
    587                     `INSERT INTO makes_change (personal_id, change_date_time, product_code)
    588            VALUES (?, datetime('now'), ?)`,
    589                     [personalId, productCode],
    590                     function(err) {
    591                         if (err) {
    592                             database.run('ROLLBACK');
    593                             callback(err);
    594                             return;
    595                         }
    596 
    597                         // Delete the product (cascades to image, color)
    598                         database.run(
    599                             'DELETE FROM product WHERE code = ? AND store_id = ?',
    600                             [productCode, storeId],
    601                             function(err) {
    602                                 if (err) {
    603                                     database.run('ROLLBACK');
    604                                     callback(err);
    605                                 } else {
    606                                     database.run('COMMIT', callback);
    607                                 }
    608                             }
    609                         );
    610                     }
    611                 );
    612             }
    613         );
    614     });
    615 }
    616 
    617 // Category functions
    618 function getCategories(callback) {
    619     database.all('SELECT * FROM category ORDER BY name', [], (err, rows) => {
    620         callback(err, rows || []);
    621     });
    622 }
    623 
    624 function getCategoriesWithParents(callback) {
    625     database.all(
    626         `SELECT c1.*, c2.name as parent_name
    627      FROM category c1
    628      LEFT JOIN category c2 ON c1.parent_category_id = c2.category_id
    629      ORDER BY c1.name`,
    630         [],
    631         (err, rows) => {
    632             callback(err, rows || []);
    633         }
    634     );
    635 }
    636 
    637 function createCategory(categoryData, callback) {
    638     database.run(
    639         'INSERT INTO category (name, description, parent_category_id) VALUES (?, ?, ?)',
    640         [categoryData.name, categoryData.description || null, categoryData.parent_id || null],
    641         function(err) {
    642             if (err) {
    643                 callback(err, null);
    644             } else {
     1232
    6451233                callback(null, {
    646                     id: this.lastID,
     1234                    id: result.rows[0].category_id,
    6471235                    name: categoryData.name,
    6481236                    parent_id: categoryData.parent_id,
    … …  
    6541242}
    6551243
    656 // Store functions
     1244
     1245/*
     1246 * ============================================================
     1247 * STORE FUNCTIONS
     1248 * ============================================================
     1249 */
     1250
    6571251function getStores(callback) {
    658     database.all('SELECT * FROM store ORDER BY name', [], (err, rows) => {
    659         callback(err, rows || []);
    660     });
    661 }
     1252
     1253    query(
     1254        `SELECT *
     1255         FROM store
     1256         ORDER BY name`,
     1257        [],
     1258        (err, result) => {
     1259
     1260            callback(
     1261                err,
     1262                result ? result.rows : []
     1263            );
     1264        }
     1265    );
     1266}
     1267
    6621268
    6631269function getStoreProducts(storeId, callback) {
    664     database.all(
    665         `SELECT p.*, c.name as category_name
    666      FROM product p
    667      JOIN category c ON p.category_id = c.category_id
    668      WHERE p.store_id = ?
    669      ORDER BY p.code`,
     1270
     1271    query(
     1272        `SELECT
     1273            p.*,
     1274            c.name AS category_name
     1275         FROM product p
     1276         JOIN category c
     1277           ON p.category_id = c.category_id
     1278         WHERE p.store_id = $1
     1279         ORDER BY p.code`,
    6701280        [storeId],
    671         (err, rows) => {
     1281        (err, result) => {
     1282
     1283            callback(
     1284                err,
     1285                result ? result.rows : []
     1286            );
     1287        }
     1288    );
     1289}
     1290
     1291
     1292function getStoreOrders(storeId, callback) {
     1293
     1294    query(
     1295        `SELECT
     1296            o.*,
     1297            c.first_name,
     1298            c.last_name
     1299         FROM "order" o
     1300         JOIN client c
     1301           ON o.client_id = c.client_id
     1302         WHERE o.store_id = $1
     1303         ORDER BY o.order_date DESC`,
     1304        [storeId],
     1305        (err, result) => {
     1306
    6721307            if (err) {
    6731308                callback(err, null);
    674             } else {
    675                 callback(null, rows || []);
    676             }
    677         }
    678     );
    679 }
    680 
    681 function getStoreOrders(storeId, callback) {
    682     database.all(
    683         `SELECT o.*, c.first_name, c.last_name
    684      FROM "order" o
    685      JOIN client c ON o.client_id = c.client_id
    686      WHERE o.store_id = ?
    687      ORDER BY o.order_date DESC`,
     1309                return;
     1310            }
     1311
     1312            const orders = result.rows || [];
     1313
     1314            if (orders.length === 0) {
     1315                callback(null, []);
     1316                return;
     1317            }
     1318
     1319            let completed = 0;
     1320
     1321            orders.forEach(order => {
     1322
     1323                query(
     1324                    `SELECT
     1325                        oi.*,
     1326                        p.description
     1327                     FROM order_items oi
     1328                     JOIN product p
     1329                       ON oi.product_code = p.code
     1330                     WHERE oi.order_num = $1`,
     1331                    [order.order_num],
     1332                    (err, result) => {
     1333
     1334                        if (!err) {
     1335                            order.items =
     1336                                result.rows || [];
     1337                        } else {
     1338                            order.items = [];
     1339                        }
     1340
     1341                        completed++;
     1342
     1343                        if (completed === orders.length) {
     1344                            callback(null, orders);
     1345                        }
     1346                    }
     1347                );
     1348            });
     1349        }
     1350    );
     1351}
     1352
     1353
     1354function getStoreEmployees(storeId, callback) {
     1355
     1356    query(
     1357        `SELECT
     1358            p.*,
     1359            e.date_of_hire,
     1360            perm.type AS permission_type,
     1361            perm.authorisation
     1362         FROM personal p
     1363         JOIN works_in_store w
     1364           ON p.id = w.personal_id
     1365         LEFT JOIN employees e
     1366           ON p.id = e.employee_id
     1367         LEFT JOIN permissions perm
     1368           ON p.id = perm.personal_id
     1369         WHERE w.store_id = $1`,
    6881370        [storeId],
    689         (err, rows) => {
     1371        (err, result) => {
     1372
     1373            callback(
     1374                err,
     1375                result ? result.rows : []
     1376            );
     1377        }
     1378    );
     1379}
     1380
     1381
     1382function getStoreReports(storeId, callback) {
     1383
     1384    query(
     1385        `SELECT *
     1386         FROM report
     1387         WHERE store_id = $1
     1388         ORDER BY generated_at DESC`,
     1389        [storeId],
     1390        (err, result) => {
     1391
     1392            callback(
     1393                err,
     1394                result ? result.rows : []
     1395            );
     1396        }
     1397    );
     1398}
     1399
     1400
     1401function getStoreStats(storeId, callback) {
     1402
     1403    const stats = {};
     1404
     1405    query(
     1406        `SELECT COUNT(*) AS total_products
     1407         FROM product
     1408         WHERE store_id = $1`,
     1409        [storeId],
     1410        (err, result) => {
     1411
    6901412            if (err) {
    6911413                callback(err, null);
    692             } else {
    693                 // Get order items for each order
    694                 let completed = 0;
    695                 const orders = rows || [];
    696 
    697                 if (orders.length === 0) {
    698                     callback(null, []);
    699                     return;
    700                 }
    701 
    702                 orders.forEach(order => {
    703                     database.all(
    704                         `SELECT oi.*, p.description
    705              FROM order_items oi
    706              JOIN product p ON oi.product_code = p.code
    707              WHERE oi.order_num = ?`,
    708                         [order.order_num],
    709                         (err, items) => {
    710                             if (!err) {
    711                                 order.items = items || [];
    712                             } else {
    713                                 order.items = [];
     1414                return;
     1415            }
     1416
     1417            stats.total_products =
     1418                result.rows[0]
     1419                    ? Number(result.rows[0].total_products)
     1420                    : 0;
     1421
     1422            query(
     1423                `SELECT COUNT(*) AS total_orders
     1424                 FROM "order"
     1425                 WHERE store_id = $1`,
     1426                [storeId],
     1427                (err, result) => {
     1428
     1429                    if (err) {
     1430                        callback(err, null);
     1431                        return;
     1432                    }
     1433
     1434                    stats.total_orders =
     1435                        result.rows[0]
     1436                            ? Number(result.rows[0].total_orders)
     1437                            : 0;
     1438
     1439                    query(
     1440                        `SELECT
     1441                            COALESCE(
     1442                                SUM(
     1443                                    oi.price * oi.quantity
     1444                                ),
     1445                                0
     1446                            ) AS total_revenue
     1447                         FROM order_items oi
     1448                         JOIN "order" o
     1449                           ON oi.order_num =
     1450                              o.order_num
     1451                         WHERE o.store_id = $1`,
     1452                        [storeId],
     1453                        (err, result) => {
     1454
     1455                            if (err) {
     1456                                callback(err, null);
     1457                                return;
    7141458                            }
    715                             completed++;
    716                             if (completed === orders.length) {
    717                                 callback(null, orders);
    718                             }
    719                         }
    720                     );
    721                 });
    722             }
    723         }
    724     );
    725 }
    726 
    727 function getStoreEmployees(storeId, callback) {
    728     database.all(
    729         `SELECT p.*, e.date_of_hire, perm.type as permission_type, perm.authorisation
    730      FROM personal p
    731      JOIN works_in_store w ON p.id = w.personal_id
    732      LEFT JOIN employees e ON p.id = e.employee_id
    733      LEFT JOIN permissions perm ON p.id = perm.personal_id
    734      WHERE w.store_id = ?`,
    735         [storeId],
    736         (err, rows) => {
    737             callback(err, rows || []);
    738         }
    739     );
    740 }
    741 
    742 function getStoreReports(storeId, callback) {
    743     database.all(
    744         `SELECT * FROM report
    745      WHERE store_id = ?
    746      ORDER BY generated_at DESC`,
    747         [storeId],
    748         (err, rows) => {
    749             callback(err, rows || []);
    750         }
    751     );
    752 }
    753 
    754 function getStoreStats(storeId, callback) {
    755     const stats = {};
    756 
    757     // Get total products
    758     database.get(
    759         'SELECT COUNT(*) as total_products FROM product WHERE store_id = ?',
    760         [storeId],
    761         (err, row) => {
    762             stats.total_products = row ? row.total_products : 0;
    763 
    764             // Get total orders
    765             database.get(
    766                 'SELECT COUNT(*) as total_orders FROM "order" WHERE store_id = ?',
    767                 [storeId],
    768                 (err, row) => {
    769                     stats.total_orders = row ? row.total_orders : 0;
    770 
    771                     // Get total revenue
    772                     database.get(
    773                         `SELECT SUM(oi.price * oi.quantity) as total_revenue
    774              FROM order_items oi
    775              JOIN "order" o ON oi.order_num = o.order_num
    776              WHERE o.store_id = ?`,
    777                         [storeId],
    778                         (err, row) => {
    779                             stats.total_revenue = row && row.total_revenue ? row.total_revenue : 0;
    780 
    781                             // Get average rating
    782                             database.get(
    783                                 `SELECT AVG(rating) as avg_rating
    784                  FROM review r
    785                  JOIN product p ON r.product_code = p.code
    786                  WHERE p.store_id = ?`,
     1459
     1460                            stats.total_revenue =
     1461                                Number(
     1462                                    result.rows[0]
     1463                                        .total_revenue || 0
     1464                                );
     1465
     1466                            query(
     1467                                `SELECT
     1468                                    COALESCE(
     1469                                        AVG(r.rating),
     1470                                        0
     1471                                    ) AS avg_rating
     1472                                 FROM review r
     1473                                 JOIN product p
     1474                                   ON r.product_code =
     1475                                      p.code
     1476                                 WHERE p.store_id = $1`,
    7871477                                [storeId],
    788                                 (err, row) => {
    789                                     stats.avg_rating = row && row.avg_rating ? row.avg_rating : 0;
    790                                     callback(null, stats);
     1478                                (err, result) => {
     1479
     1480                                    if (err) {
     1481                                        callback(err, null);
     1482                                        return;
     1483                                    }
     1484
     1485                                    stats.avg_rating =
     1486                                        Number(
     1487                                            result.rows[0]
     1488                                                .avg_rating || 0
     1489                                        );
     1490
     1491                                    callback(
     1492                                        null,
     1493                                        stats
     1494                                    );
    7911495                                }
    7921496                            );
    … …  
    7991503}
    8001504
    801 // Order functions
     1505
     1506/*
     1507 * ============================================================
     1508 * ORDER FUNCTIONS
     1509 * ============================================================
     1510 */
     1511
    8021512function createOrderNew(orderData, callback) {
    803     database.run('BEGIN TRANSACTION', (err) => {
    804         if (err) {
     1513
     1514    pool.connect()
     1515        .then(client => {
     1516
     1517            return client.query('BEGIN')
     1518                .then(() => {
     1519
     1520                    return client.query(
     1521                        `INSERT INTO "order"
     1522                            (
     1523                                order_num,
     1524                                client_id,
     1525                                order_date,
     1526                                quantity,
     1527                                payment_method,
     1528                                discount,
     1529                                delivery_address,
     1530                                store_id
     1531                            )
     1532                         VALUES
     1533                            (
     1534                                $1,
     1535                                $2,
     1536                                NOW(),
     1537                                $3,
     1538                                $4,
     1539                                $5,
     1540                                $6,
     1541                                $7
     1542                            )`,
     1543                        [
     1544                            orderData.order_num,
     1545                            orderData.client_id,
     1546                            orderData.quantity,
     1547                            orderData.payment_method,
     1548                            orderData.discount,
     1549                            orderData.delivery_address,
     1550                            orderData.store_id
     1551                        ]
     1552                    );
     1553                })
     1554                .then(() => {
     1555
     1556                    const items =
     1557                        orderData.items || [];
     1558
     1559                    if (items.length === 0) {
     1560                        return client.query('COMMIT')
     1561                            .then(() => {
     1562
     1563                                client.release();
     1564
     1565                                callback(
     1566                                    null,
     1567                                    orderData.order_num
     1568                                );
     1569                            });
     1570                    }
     1571
     1572                    return Promise.all(
     1573                        items.map(item => {
     1574
     1575                            return client.query(
     1576                                `INSERT INTO order_items
     1577                                    (
     1578                                        order_num,
     1579                                        product_code,
     1580                                        quantity,
     1581                                        price
     1582                                    )
     1583                                 VALUES
     1584                                    ($1, $2, $3, $4)`,
     1585                                [
     1586                                    orderData.order_num,
     1587                                    item.product_code,
     1588                                    item.quantity,
     1589                                    item.price
     1590                                ]
     1591                            );
     1592                        })
     1593                    )
     1594                        .then(() => client.query('COMMIT'))
     1595                        .then(() => {
     1596
     1597                            client.release();
     1598
     1599                            callback(
     1600                                null,
     1601                                orderData.order_num
     1602                            );
     1603                        });
     1604                })
     1605                .catch(err => {
     1606
     1607                    return client.query('ROLLBACK')
     1608                        .catch(() => {})
     1609                        .then(() => {
     1610
     1611                            client.release();
     1612                            callback(err, null);
     1613                        });
     1614                });
     1615        })
     1616        .catch(err => {
    8051617            callback(err, null);
    806             return;
    807         }
    808 
    809         database.run(
    810             `INSERT INTO "order" (order_num, client_id, order_date, quantity, payment_method,
    811         discount, delivery_address, store_id)
    812        VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?)`,
    813             [
    814                 orderData.order_num,
    815                 orderData.client_id,
    816                 orderData.quantity,
    817                 orderData.payment_method,
    818                 orderData.discount,
    819                 orderData.delivery_address,
    820                 orderData.store_id
    821             ],
    822             function(err) {
    823                 if (err) {
    824                     database.run('ROLLBACK');
    825                     callback(err, null);
    826                     return;
    827                 }
    828 
    829                 let itemsInserted = 0;
    830                 const items = orderData.items || [];
    831 
    832                 if (items.length === 0) {
    833                     database.run('COMMIT');
    834                     callback(null, orderData.order_num);
    835                     return;
    836                 }
    837 
    838                 items.forEach(item => {
    839                     database.run(
    840                         `INSERT INTO order_items (order_num, product_code, quantity, price)
    841              VALUES (?, ?, ?, ?)`,
    842                         [orderData.order_num, item.product_code, item.quantity, item.price],
    843                         function(err) {
    844                             if (err) {
    845                                 database.run('ROLLBACK');
    846                                 callback(err, null);
    847                                 return;
    848                             }
    849 
    850                             itemsInserted++;
    851                             if (itemsInserted === items.length) {
    852                                 database.run('COMMIT', (err) => {
    853                                     if (err) {
    854                                         callback(err, null);
    855                                     } else {
    856                                         callback(null, orderData.order_num);
    857                                     }
    858                                 });
    859                             }
    860                         }
    861                     );
    862                 });
    863             }
    864         );
    865     });
    866 }
     1618        });
     1619}
     1620
    8671621
    8681622function getOrdersByClient(clientId, callback) {
    869     database.all(
    870         `SELECT o.*, s.name as store_name
    871      FROM "order" o
    872      JOIN store s ON o.store_id = s.store_id
    873      WHERE o.client_id = ?
    874      ORDER BY o.order_date DESC`,
     1623
     1624    query(
     1625        `SELECT
     1626            o.*,
     1627            s.name AS store_name
     1628         FROM "order" o
     1629         JOIN store s
     1630           ON o.store_id = s.store_id
     1631         WHERE o.client_id = $1
     1632         ORDER BY o.order_date DESC`,
    8751633        [clientId],
    876         (err, rows) => {
     1634        (err, result) => {
     1635
    8771636            if (err) {
    8781637                callback(err, null);
    879             } else {
    880                 // Get order items for each order
    881                 let completed = 0;
    882                 const orders = rows || [];
    883 
    884                 if (orders.length === 0) {
    885                     callback(null, []);
    886                     return;
    887                 }
    888 
    889                 orders.forEach(order => {
    890                     database.all(
    891                         `SELECT oi.*, p.description
    892              FROM order_items oi
    893              JOIN product p ON oi.product_code = p.code
    894              WHERE oi.order_num = ?`,
    895                         [order.order_num],
    896                         (err, items) => {
    897                             if (!err) {
    898                                 order.items = items || [];
    899                             } else {
    900                                 order.items = [];
    901                             }
    902                             completed++;
    903                             if (completed === orders.length) {
    904                                 callback(null, orders);
    905                             }
     1638                return;
     1639            }
     1640
     1641            const orders = result.rows || [];
     1642
     1643            if (orders.length === 0) {
     1644                callback(null, []);
     1645                return;
     1646            }
     1647
     1648            let completed = 0;
     1649
     1650            orders.forEach(order => {
     1651
     1652                query(
     1653                    `SELECT
     1654                        oi.*,
     1655                        p.description
     1656                     FROM order_items oi
     1657                     JOIN product p
     1658                       ON oi.product_code = p.code
     1659                     WHERE oi.order_num = $1`,
     1660                    [order.order_num],
     1661                    (err, result) => {
     1662
     1663                        if (!err) {
     1664                            order.items =
     1665                                result.rows || [];
     1666                        } else {
     1667                            order.items = [];
    9061668                        }
    907                     );
    908                 });
    909             }
    910         }
    911     );
    912 }
     1669
     1670                        completed++;
     1671
     1672                        if (completed === orders.length) {
     1673                            callback(null, orders);
     1674                        }
     1675                    }
     1676                );
     1677            });
     1678        }
     1679    );
     1680}
     1681
    9131682
    9141683function getAllOrders(callback) {
    915     database.all(
    916         `SELECT o.*, c.first_name, c.last_name, s.name as store_name
    917      FROM "order" o
    918      JOIN client c ON o.client_id = c.client_id
    919      JOIN store s ON o.store_id = s.store_id
    920      ORDER BY o.order_date DESC`,
     1684
     1685    query(
     1686        `SELECT
     1687            o.*,
     1688            c.first_name,
     1689            c.last_name,
     1690            s.name AS store_name
     1691         FROM "order" o
     1692         JOIN client c
     1693           ON o.client_id = c.client_id
     1694         JOIN store s
     1695           ON o.store_id = s.store_id
     1696         ORDER BY o.order_date DESC`,
    9211697        [],
    922         (err, rows) => {
    923             callback(err, rows || []);
    924         }
    925     );
    926 }
    927 
    928 // Review functions
     1698        (err, result) => {
     1699
     1700            callback(
     1701                err,
     1702                result ? result.rows : []
     1703            );
     1704        }
     1705    );
     1706}
     1707
     1708
     1709/*
     1710 * ============================================================
     1711 * REVIEW FUNCTIONS
     1712 * ============================================================
     1713 */
     1714
    9291715function createReviewNew(reviewData, callback) {
    930     database.run(
    931         `INSERT INTO review (review_id, client_id, product_code, rating, comment, review_date)
    932      VALUES (?, ?, ?, ?, ?, datetime('now'))`,
     1716
     1717    const reviewId =
     1718        'REV' +
     1719        Date.now().toString().slice(-8);
     1720
     1721    query(
     1722        `INSERT INTO review
     1723            (
     1724                review_id,
     1725                client_id,
     1726                product_code,
     1727                rating,
     1728                comment,
     1729                review_date
     1730            )
     1731         VALUES
     1732            ($1, $2, $3, $4, $5, NOW())
     1733         RETURNING review_id`,
    9331734        [
    934             'REV' + Date.now().toString().slice(-8),
     1735            reviewId,
    9351736            reviewData.client_id,
    9361737            reviewData.product_code,
    … …  
    9381739            reviewData.comment || ''
    9391740        ],
    940         function(err) {
     1741        (err, result) => {
     1742
    9411743            if (err) {
    9421744                callback(err, null);
    9431745            } else {
    944                 callback(null, this.lastID);
    945             }
    946         }
    947     );
    948 }
    949 
    950 // Request functions
     1746                callback(
     1747                    null,
     1748                    result.rows[0].review_id
     1749                );
     1750            }
     1751        }
     1752    );
     1753}
     1754
     1755
     1756/*
     1757 * ============================================================
     1758 * REQUEST FUNCTIONS
     1759 * ============================================================
     1760 */
     1761
    9511762function createRequest(requestData, callback) {
    952     database.run(
    953         `INSERT INTO request (request_num, date_and_time, problem, client_id, store_id)
    954      VALUES (?, ?, ?, ?, ?)`,
     1763
     1764    query(
     1765        `INSERT INTO request
     1766            (
     1767                request_num,
     1768                date_and_time,
     1769                problem,
     1770                client_id,
     1771                store_id
     1772            )
     1773         VALUES
     1774            ($1, $2, $3, $4, $5)`,
    9551775        [
    9561776            requestData.request_num,
    … …  
    9601780            requestData.store_id
    9611781        ],
    962         function(err) {
     1782        (err) => {
     1783
    9631784            if (err) {
    9641785                callback(err, null);
    9651786            } else {
    966                 callback(null, requestData.request_num);
    967             }
    968         }
    969     );
    970 }
    971 
    972 // Refund functions
     1787                callback(
     1788                    null,
     1789                    requestData.request_num
     1790                );
     1791            }
     1792        }
     1793    );
     1794}
     1795
     1796
     1797/*
     1798 * ============================================================
     1799 * REFUND FUNCTIONS
     1800 * ============================================================
     1801 */
     1802
    9731803function createRefund(refundData, callback) {
    974     database.run(
    975         `INSERT INTO refund (refund_id, order_num, amount, reason, request_date)
    976      VALUES (?, ?, ?, ?, datetime('now'))`,
     1804
     1805    query(
     1806        `INSERT INTO refund
     1807            (
     1808                refund_id,
     1809                order_num,
     1810                amount,
     1811                reason,
     1812                request_date
     1813            )
     1814         VALUES
     1815            ($1, $2, $3, $4, NOW())`,
    9771816        [
    9781817            refundData.refund_id,
    … …  
    9811820            refundData.reason
    9821821        ],
    983         function(err) {
     1822        (err) => {
     1823
    9841824            if (err) {
    9851825                callback(err, null);
    9861826            } else {
    987                 callback(null, refundData.refund_id);
    988             }
    989         }
    990     );
    991 }
    992 
    993 // Employee task functions
    994 function getEmployeeTasks(personalId, storeId, callback) {
     1827                callback(
     1828                    null,
     1829                    refundData.refund_id
     1830                );
     1831            }
     1832        }
     1833    );
     1834}
     1835
     1836
     1837/*
     1838 * ============================================================
     1839 * EMPLOYEE TASKS
     1840 * ============================================================
     1841 */
     1842
     1843function getEmployeeTasks(
     1844    personalId,
     1845    storeId,
     1846    callback
     1847) {
     1848
    9951849    const tasks = {
    9961850        pending_orders: [],
    … …  
    9991853    };
    10001854
    1001     // Get pending orders
    1002     database.all(
    1003         `SELECT o.*, c.first_name, c.last_name
    1004      FROM "order" o
    1005      JOIN client c ON o.client_id = c.client_id
    1006      WHERE o.store_id = ? AND o.status = 'pending'
    1007      ORDER BY o.order_date ASC`,
    1008         [storeId],
    1009         (err, rows) => {
     1855    /*
     1856     * Pending orders
     1857     */
     1858    query(
     1859        `SELECT
     1860            o.*,
     1861            c.first_name,
     1862            c.last_name
     1863         FROM "order" o
     1864         JOIN client c
     1865           ON o.client_id = c.client_id
     1866         WHERE o.store_id = $1
     1867           AND o.status = $2
     1868         ORDER BY o.order_date ASC`,
     1869        [
     1870            storeId,
     1871            'pending'
     1872        ],
     1873        (err, result) => {
     1874
    10101875            if (!err) {
    1011                 tasks.pending_orders = rows || [];
    1012             }
    1013 
    1014             // Get pending requests
    1015             database.all(
    1016                 `SELECT r.*, c.first_name, c.last_name
    1017          FROM request r
    1018          JOIN client c ON r.client_id = c.client_id
    1019          WHERE r.store_id = ? AND r.status = 'pending'
    1020          ORDER BY r.date_and_time ASC`,
    1021                 [storeId],
    1022                 (err, rows) => {
     1876                tasks.pending_orders =
     1877                    result.rows || [];
     1878            }
     1879
     1880            /*
     1881             * Pending requests
     1882             */
     1883            query(
     1884                `SELECT
     1885                    r.*,
     1886                    c.first_name,
     1887                    c.last_name
     1888                 FROM request r
     1889                 JOIN client c
     1890                   ON r.client_id = c.client_id
     1891                 WHERE r.store_id = $1
     1892                   AND r.status = $2
     1893                 ORDER BY r.date_and_time ASC`,
     1894                [
     1895                    storeId,
     1896                    'pending'
     1897                ],
     1898                (err, result) => {
     1899
    10231900                    if (!err) {
    1024                         tasks.pending_requests = rows || [];
     1901                        tasks.pending_requests =
     1902                            result.rows || [];
    10251903                    }
    10261904
    1027                     // Get pending refunds
    1028                     database.all(
    1029                         `SELECT rf.*, o.client_id, c.first_name, c.last_name
    1030              FROM refund rf
    1031              JOIN "order" o ON rf.order_num = o.order_num
    1032              JOIN client c ON o.client_id = c.client_id
    1033              WHERE o.store_id = ? AND rf.status = 'pending'
    1034              ORDER BY rf.request_date ASC`,
    1035                         [storeId],
    1036                         (err, rows) => {
     1905                    /*
     1906                     * Pending refunds
     1907                     */
     1908                    query(
     1909                        `SELECT
     1910                            rf.*,
     1911                            o.client_id,
     1912                            c.first_name,
     1913                            c.last_name
     1914                         FROM refund rf
     1915                         JOIN "order" o
     1916                           ON rf.order_num =
     1917                              o.order_num
     1918                         JOIN client c
     1919                           ON o.client_id =
     1920                              c.client_id
     1921                         WHERE o.store_id = $1
     1922                           AND rf.status = $2
     1923                         ORDER BY rf.request_date ASC`,
     1924                        [
     1925                            storeId,
     1926                            'pending'
     1927                        ],
     1928                        (err, result) => {
     1929
    10371930                            if (!err) {
    1038                                 tasks.pending_refunds = rows || [];
     1931                                tasks.pending_refunds =
     1932                                    result.rows || [];
    10391933                            }
    1040                             callback(null, tasks);
     1934
     1935                            callback(
     1936                                null,
     1937                                tasks
     1938                            );
    10411939                        }
    10421940                    );
    … …  
    10471945}
    10481946
    1049 // Client stats functions
     1947
     1948/*
     1949 * ============================================================
     1950 * CLIENT STATISTICS
     1951 * ============================================================
     1952 */
     1953
    10501954function getClientStats(clientId, callback) {
     1955
    10511956    const stats = {};
    10521957
    1053     // Get total orders
    1054     database.get(
    1055         'SELECT COUNT(*) as total_orders FROM "order" WHERE client_id = ?',
     1958    query(
     1959        `SELECT COUNT(*) AS total_orders
     1960         FROM "order"
     1961         WHERE client_id = $1`,
    10561962        [clientId],
    1057         (err, row) => {
    1058             stats.total_orders = row ? row.total_orders : 0;
    1059 
    1060             // Get total spent
    1061             database.get(
    1062                 `SELECT SUM(oi.price * oi.quantity) as total_spent
    1063          FROM order_items oi
    1064          JOIN "order" o ON oi.order_num = o.order_num
    1065          WHERE o.client_id = ?`,
     1963        (err, result) => {
     1964
     1965            if (err) {
     1966                callback(err, null);
     1967                return;
     1968            }
     1969
     1970            stats.total_orders =
     1971                Number(
     1972                    result.rows[0]
     1973                        ? result.rows[0].total_orders
     1974                        : 0
     1975                );
     1976
     1977            query(
     1978                `SELECT
     1979                    COALESCE(
     1980                        SUM(
     1981                            oi.price * oi.quantity
     1982                        ),
     1983                        0
     1984                    ) AS total_spent
     1985                 FROM order_items oi
     1986                 JOIN "order" o
     1987                   ON oi.order_num = o.order_num
     1988                 WHERE o.client_id = $1`,
    10661989                [clientId],
    1067                 (err, row) => {
    1068                     stats.total_spent = row && row.total_spent ? row.total_spent : 0;
    1069 
    1070                     // Get pending orders
    1071                     database.get(
    1072                         'SELECT COUNT(*) as pending_orders FROM "order" WHERE client_id = ? AND status = "pending"',
    1073                         [clientId],
    1074                         (err, row) => {
    1075                             stats.pending_orders = row ? row.pending_orders : 0;
    1076 
    1077                             // Get delivered orders
    1078                             database.get(
    1079                                 'SELECT COUNT(*) as delivered_orders FROM "order" WHERE client_id = ? AND status = "delivered"',
    1080                                 [clientId],
    1081                                 (err, row) => {
    1082                                     stats.delivered_orders = row ? row.delivered_orders : 0;
    1083                                     callback(null, stats);
     1990                (err, result) => {
     1991
     1992                    if (err) {
     1993                        callback(err, null);
     1994                        return;
     1995                    }
     1996
     1997                    stats.total_spent =
     1998                        Number(
     1999                            result.rows[0]
     2000                                ? result.rows[0].total_spent
     2001                                : 0
     2002                        );
     2003
     2004                    query(
     2005                        `SELECT COUNT(*) AS pending_orders
     2006                         FROM "order"
     2007                         WHERE client_id = $1
     2008                           AND status = $2`,
     2009                        [
     2010                            clientId,
     2011                            'pending'
     2012                        ],
     2013                        (err, result) => {
     2014
     2015                            if (err) {
     2016                                callback(err, null);
     2017                                return;
     2018                            }
     2019
     2020                            stats.pending_orders =
     2021                                Number(
     2022                                    result.rows[0]
     2023                                        ? result.rows[0]
     2024                                            .pending_orders
     2025                                        : 0
     2026                                );
     2027
     2028                            query(
     2029                                `SELECT
     2030                                    COUNT(*) AS delivered_orders
     2031                                 FROM "order"
     2032                                 WHERE client_id = $1
     2033                                   AND status = $2`,
     2034                                [
     2035                                    clientId,
     2036                                    'delivered'
     2037                                ],
     2038                                (err, result) => {
     2039
     2040                                    if (err) {
     2041                                        callback(err, null);
     2042                                        return;
     2043                                    }
     2044
     2045                                    stats.delivered_orders =
     2046                                        Number(
     2047                                            result.rows[0]
     2048                                                ? result.rows[0]
     2049                                                    .delivered_orders
     2050                                                : 0
     2051                                        );
     2052
     2053                                    callback(
     2054                                        null,
     2055                                        stats
     2056                                    );
    10842057                                }
    10852058                            );
    … …  
    10922065}
    10932066
    1094 // User functions for admin
     2067
     2068/*
     2069 * ============================================================
     2070 * ADMIN USER FUNCTIONS
     2071 * ============================================================
     2072 */
     2073
    10952074function getAllUsers(callback) {
    1096     const usersMap = new Map(); // Use Map to deduplicate by ID
    1097 
    1098     // Get client users
    1099     database.all(
    1100         `SELECT client_id as id, first_name, last_name, email, 'client' as user_type,
    1101                 NULL as username, NULL as role_priority
     2075
     2076    query(
     2077        `SELECT
     2078            client_id AS id,
     2079            first_name,
     2080            last_name,
     2081            email,
     2082            'client' AS user_type,
     2083            NULL AS username,
     2084            5 AS role_priority
    11022085         FROM client
    11032086         ORDER BY client_id`,
    11042087        [],
    1105         (err, rows) => {
    1106             if (!err && rows) {
    1107                 rows.forEach(row => {
    1108                     // Clients have lowest priority (5)
    1109                     row.role_priority = 5;
    1110                     usersMap.set(row.id, row);
    1111                 });
    1112             }
    1113 
    1114             // Get personal users (employees and store owners)
    1115             database.all(
    1116                 `SELECT p.id, p.first_name, p.last_name, p.email,
    1117                         CASE
    1118                             WHEN b.boss_id IS NOT NULL THEN 'store_owner'
    1119                             ELSE 'store_employee'
    1120                         END as user_type,
    1121                         NULL as username,
    1122                         CASE
    1123                             WHEN b.boss_id IS NOT NULL THEN 2  -- store_owner priority 2
    1124                             ELSE 4                             -- store_employee priority 4
    1125                         END as role_priority
     2088        (err, result) => {
     2089
     2090            if (err) {
     2091                callback(err, null);
     2092                return;
     2093            }
     2094
     2095            const usersMap = new Map();
     2096
     2097            result.rows.forEach(row => {
     2098                usersMap.set(row.id, row);
     2099            });
     2100
     2101            /*
     2102             * Personal users
     2103             */
     2104            query(
     2105                `SELECT
     2106                    p.id,
     2107                    p.first_name,
     2108                    p.last_name,
     2109                    p.email,
     2110                    CASE
     2111                        WHEN b.boss_id IS NOT NULL
     2112                            THEN 'store_owner'
     2113                        ELSE 'store_employee'
     2114                    END AS user_type,
     2115                    NULL AS username,
     2116                    CASE
     2117                        WHEN b.boss_id IS NOT NULL
     2118                            THEN 2
     2119                        ELSE 4
     2120                    END AS role_priority
    11262121                 FROM personal p
    1127                  LEFT JOIN boss b ON p.id = b.boss_id
    1128                  LEFT JOIN employees e ON p.id = e.employee_id
    1129                  WHERE b.boss_id IS NOT NULL OR e.employee_id IS NOT NULL
     2122                 LEFT JOIN boss b
     2123                   ON p.id = b.boss_id
     2124                 LEFT JOIN employees e
     2125                   ON p.id = e.employee_id
     2126                 WHERE b.boss_id IS NOT NULL
     2127                    OR e.employee_id IS NOT NULL
    11302128                 ORDER BY p.id`,
    11312129                [],
    1132                 (err, rows) => {
    1133                     if (!err && rows) {
    1134                         rows.forEach(row => {
    1135                             // Only add if not exists or current has higher priority (lower number)
    1136                             const existing = usersMap.get(row.id);
    1137                             if (!existing || (existing.role_priority && row.role_priority < existing.role_priority)) {
    1138                                 usersMap.set(row.id, row);
    1139                             }
    1140                         });
     2130                (err, result) => {
     2131
     2132                    if (err) {
     2133                        callback(err, null);
     2134                        return;
    11412135                    }
    11422136
    1143                     // Get system users (including admin)
    1144                     database.all(
    1145                         `SELECT id, username, email, user_type,
    1146                                 CASE
    1147                                     WHEN user_type = 'admin' THEN 1  -- admin highest priority
    1148                                     ELSE 3                            -- other system users priority 3
    1149                                 END as role_priority
     2137                    result.rows.forEach(row => {
     2138
     2139                        const existing =
     2140                            usersMap.get(row.id);
     2141
     2142                        if (
     2143                            !existing ||
     2144                            (
     2145                                existing.role_priority &&
     2146                                row.role_priority <
     2147                                existing.role_priority
     2148                            )
     2149                        ) {
     2150                            usersMap.set(
     2151                                row.id,
     2152                                row
     2153                            );
     2154                        }
     2155                    });
     2156
     2157                    /*
     2158                     * System users
     2159                     */
     2160                    query(
     2161                        `SELECT
     2162                            id,
     2163                            username,
     2164                            email,
     2165                            user_type,
     2166                            CASE
     2167                                WHEN user_type = 'admin'
     2168                                    THEN 1
     2169                                ELSE 3
     2170                            END AS role_priority
    11502171                         FROM users
    11512172                         ORDER BY id`,
    11522173                        [],
    1153                         (err, rows) => {
    1154                             if (!err && rows) {
    1155                                 rows.forEach(row => {
    1156                                     // System users have priority based on type
    1157                                     const existing = usersMap.get(row.id);
    1158                                     if (!existing || (existing.role_priority && row.role_priority < existing.role_priority)) {
    1159                                         // For system users, format the response properly
    1160                                         const userData = {
    1161                                             id: row.id,
    1162                                             username: row.username,
    1163                                             email: row.email,
    1164                                             user_type: row.user_type,
    1165                                             role_priority: row.role_priority
    1166                                         };
    1167                                         // Add first_name/last_name if not present
    1168                                         if (row.user_type === 'admin') {
    1169                                             userData.first_name = 'Admin';
    1170                                             userData.last_name = 'User';
    1171                                         }
    1172                                         usersMap.set(row.id, userData);
     2174                        (err, result) => {
     2175
     2176                            if (err) {
     2177                                callback(err, null);
     2178                                return;
     2179                            }
     2180
     2181                            result.rows.forEach(row => {
     2182
     2183                                const existing =
     2184                                    usersMap.get(row.id);
     2185
     2186                                if (
     2187                                    !existing ||
     2188                                    (
     2189                                        existing.role_priority &&
     2190                                        row.role_priority <
     2191                                        existing.role_priority
     2192                                    )
     2193                                ) {
     2194
     2195                                    const userData = {
     2196                                        id: row.id,
     2197                                        username: row.username,
     2198                                        email: row.email,
     2199                                        user_type: row.user_type,
     2200                                        role_priority: row.role_priority
     2201                                    };
     2202
     2203                                    if (
     2204                                        row.user_type ===
     2205                                        'admin'
     2206                                    ) {
     2207                                        userData.first_name =
     2208                                            'Admin';
     2209
     2210                                        userData.last_name =
     2211                                            'User';
    11732212                                    }
     2213
     2214                                    usersMap.set(
     2215                                        row.id,
     2216                                        userData
     2217                                    );
     2218                                }
     2219                            });
     2220
     2221                            const users =
     2222                                Array.from(
     2223                                    usersMap.values()
     2224                                ).map(user => {
     2225
     2226                                    const {
     2227                                        role_priority,
     2228                                        ...userWithoutPriority
     2229                                    } = user;
     2230
     2231                                    return userWithoutPriority;
    11742232                                });
    1175                             }
    1176 
    1177                             // Convert Map to array and remove role_priority before sending
    1178                             const users = Array.from(usersMap.values()).map(user => {
    1179                                 const { role_priority, ...userWithoutPriority } = user;
    1180                                 return userWithoutPriority;
    1181                             });
    1182 
    1183                             callback(null, users);
     2233
     2234                            callback(
     2235                                null,
     2236                                users
     2237                            );
    11842238                        }
    11852239                    );
    … …  
    11902244}
    11912245
    1192 // Audit log function
    1193 function logAudit(userId, action, resourceType, resourceId, details, ipAddress) {
    1194     database.run(
    1195         `INSERT INTO audit_log (user_id, action, resource_type, resource_id, details, ip_address)
    1196      VALUES (?, ?, ?, ?, ?, ?)`,
    1197         [userId, action, resourceType, resourceId, details, ipAddress],
     2246
     2247/*
     2248 * ============================================================
     2249 * AUDIT LOG
     2250 * ============================================================
     2251 */
     2252
     2253function logAudit(
     2254    userId,
     2255    action,
     2256    resourceType,
     2257    resourceId,
     2258    details,
     2259    ipAddress
     2260) {
     2261
     2262    query(
     2263        `INSERT INTO audit_log
     2264            (
     2265                user_id,
     2266                action,
     2267                resource_type,
     2268                resource_id,
     2269                details,
     2270                ip_address
     2271            )
     2272         VALUES
     2273            ($1, $2, $3, $4, $5, $6)`,
     2274        [
     2275            userId,
     2276            action,
     2277            resourceType,
     2278            resourceId,
     2279            details,
     2280            ipAddress
     2281        ],
    11982282        (err) => {
     2283
    11992284            if (err) {
    1200                 console.error('Error logging audit:', err);
    1201             }
    1202         }
    1203     );
    1204 }
     2285                console.error(
     2286                    'Error logging audit:',
     2287                    err
     2288                );
     2289            }
     2290        }
     2291    );
     2292}
     2293
     2294
     2295/*
     2296 * ============================================================
     2297 * EXPORTS
     2298 * ============================================================
     2299 */
    12052300
    12062301module.exports = {
    1207     database,
     2302
     2303    pool,
     2304
     2305    query,
     2306
    12082307    ensureGeneralCategory,
    12092308    getGeneralCategoryId,
     2309
    12102310    getUserByUsername,
    12112311    getUserById,
    12122312    createUser,
     2313
    12132314    getClientByEmail,
    12142315    getClientById,
    12152316    createClient,
    12162317    verifyClientPassword,
     2318
    12172319    getPersonalByEmail,
    12182320    getPersonalById,
    12192321    verifyPassword,
    12202322    updatePasswordAndClearForce,
     2323
    12212324    getProducts,
    12222325    getProductById,
    … …  
    12252328    updateProduct,
    12262329    deleteProduct,
     2330
    12272331    getCategories,
    12282332    getCategoriesWithParents,
    12292333    createCategory,
     2334
    12302335    getStores,
    12312336    getStoreProducts,
    … …  
    12342339    getStoreReports,
    12352340    getStoreStats,
     2341
    12362342    createOrderNew,
    12372343    getOrdersByClient,
    12382344    getAllOrders,
     2345
    12392346    createReviewNew,
     2347
    12402348    createRequest,
     2349
    12412350    createRefund,
     2351
    12422352    getEmployeeTasks,
     2353
    12432354    getClientStats,
     2355
    12442356    getAllUsers,
     2357
    12452358    logAudit
    12462359};
  • package.json

    r6fea37e r6149556  
    1313    "dotenv": "^16.3.1",
    1414    "nodemailer": "^6.9.7",
    15     "sqlite3": "^5.1.6"
     15    "pg": "^8.23.0"
    1616  },
    1717  "devDependencies": {
  • server.js

    r6fea37e r6149556  
    11const http = require('http');
    22const url = require('url');
    3 const database = require('./database.js');
     3const { Pool } = require('pg');
    44const fs = require('fs');
    55const path = require('path');
    … …  
    2525    const emailConfig = {
    2626        host: process.env.SMTP_HOST || 'smtp.gmail.com',
    27         port: parseInt(process.env.SMTP_PORT) || 587,
     27        port: (() => {
     28            const configuredPort = parseInt(process.env.SMTP_PORT, 10);
     29            if (process.env.SMTP_HOST === 'smtp.ethereal.email' && configuredPort === 3000) return 587;
     30            return configuredPort || 587;
     31        })(),
    2832        secure: false,
    2933        auth: {
    … …  
    169173    let code = '';
    170174    for(let i = 0; i < 6; i++) {
    171         code += crypto.randomInt(0, 9);
     175        code += crypto.randomInt(0, 10);
    172176    }
    173177    return code;
    … …  
    318322
    319323                database.database.get(
    320                     'SELECT boss_id FROM boss WHERE boss_id = ?',
     324                    'SELECT boss_id FROM boss WHERE boss_id = $1',
    321325                    [personalId],
    322326                    (err, boss) => {
    … …  
    339343}
    340344
    341 // Database initialization function
    342 async function initializeDatabase() {
    343     console.log('šŸ” Checking database schema...');
    344 
    345     // List of all required tables
    346     const requiredTables = [
    347         'client',
    348         'store',
    349         'category',
    350         'users',
    351         'personal',
    352         'product',
    353         'boss',
    354         'employees',
    355         'works_in_store',
    356         'permissions',
    357         'order',
    358         'order_items',
    359         'review',
    360         'request',
    361         'refund',
    362         'report',
    363         'audit_log',
    364         'color',
    365         'image',
    366         'delivery_address',
    367         'roles',
    368         'user_roles'
    369     ];
    370 
    371     try {
    372         // For SQLite, we need to use a different approach to check tables
    373         const result = await new Promise((resolve, reject) => {
    374             database.database.all(
    375                 "SELECT name FROM sqlite_master WHERE type='table'",
    376                 [],
    377                 (err, rows) => {
    378                     if (err) reject(err);
    379                     else resolve(rows || []);
    380                 }
    381             );
    382         });
    383 
    384         const existingTables = result.map(row => row.name);
    385         const missingTables = requiredTables.filter(table => !existingTables.includes(table));
    386 
    387         if (missingTables.length > 0) {
    388             console.log(`āš ļø Missing tables: ${missingTables.join(', ')}`);
    389             console.log('šŸ”„ Recreating entire database...');
    390 
    391             // Drop all tables in correct order (respecting foreign keys)
    392             await dropAllTables();
    393 
    394             // Create all tables
    395             await createAllTables();
    396 
    397             // Create indexes
    398             await createIndexes();
    399 
    400             // Insert initial data
    401             await insertInitialData();
    402 
    403             console.log('āœ… Database recreation completed');
    404         } else {
    405             console.log('āœ… All required tables exist');
    406             // Even if tables exist, ensure admin user exists with ID 000000
    407             await ensureAdminUser();
    408         }
    409     } catch (err) {
    410         console.error('āŒ Error checking database schema:', err);
    411         console.log('āš ļø Attempting to recreate database anyway...');
    412 
    413         try {
    414             await dropAllTables();
    415             await createAllTables();
    416             await createIndexes();
    417             await insertInitialData();
    418             console.log('āœ… Database recreation completed');
    419         } catch (createErr) {
    420             console.error('āŒ Failed to recreate database:', createErr);
    421         }
    422     }
     345
     346
     347const pool = new Pool({
     348    connectionString: process.env.DATABASE_URL,
     349    host: process.env.PGHOST || process.env.DB_HOST || 'localhost',
     350    port: Number(process.env.PGPORT || process.env.DB_PORT || 5432),
     351    user: process.env.PGUSER || process.env.DB_USER || 'postgres',
     352    password: process.env.PGPASSWORD || process.env.DB_PASSWORD || '',
     353    database: process.env.PGDATABASE || process.env.DB_NAME || 'handcraft_marketplace',
     354    max: Number(process.env.PG_POOL_MAX || 10),
     355    idleTimeoutMillis: 30000
     356});
     357
     358let transactionClient = null;
     359
     360function dbQuery(sql, params = [], callback) {
     361    const client = transactionClient || pool;
     362    client.query(sql, params)
     363        .then(result => callback(null, result))
     364        .catch(err => callback(err));
    423365}
    424366
    425 // Function to ensure admin user exists with ID 000000
    426 function ensureAdminUser() {
    427     return new Promise((resolve) => {
    428         database.database.get(
    429             'SELECT * FROM users WHERE id = ? OR username = ? OR email = ?',
    430             ['000000', 'admin', 'admin@handcraft.com'],
    431             (err, existingAdmin) => {
    432                 if (err) {
    433                     console.error('Error checking for existing admin:', err.message);
    434                     resolve();
     367const database = {
     368    database: {
     369        get(sql, params, callback) {
     370            if (typeof params === 'function') {
     371                callback = params;
     372                params = [];
     373            }
     374            dbQuery(sql, params || [], (err, result) => {
     375                callback(err, result && result.rows ? result.rows[0] : undefined);
     376            });
     377        },
     378        all(sql, params, callback) {
     379            if (typeof params === 'function') {
     380                callback = params;
     381                params = [];
     382            }
     383            dbQuery(sql, params || [], (err, result) => {
     384                callback(err, result ? result.rows : []);
     385            });
     386        },
     387        run(sql, params, callback) {
     388            if (typeof params === 'function') {
     389                callback = params;
     390                params = [];
     391            }
     392            const normalized = String(sql).trim().replace(/;\s*$/, '').toUpperCase();
     393
     394            if (normalized === 'BEGIN TRANSACTION' || normalized === 'BEGIN') {
     395                if (transactionClient) {
     396                    callback?.(null);
    435397                    return;
    436398                }
    437 
    438                 // Insert admin user if it doesn't exist
    439                 if (!existingAdmin) {
    440                     const adminId = '000000';
    441                     const adminPassword = bcrypt.hashSync('Admin123!', 10);
    442 
    443                     // Start a transaction
    444                     database.database.run('BEGIN TRANSACTION', (err) => {
    445                         if (err) {
    446                             console.error('Error beginning transaction:', err);
    447                             resolve();
    448                             return;
    449                         }
    450 
    451                         // Insert into users table
    452                         database.database.run(
    453                             `INSERT INTO users (id, username, email, password, user_type, force_password_change)
    454                              VALUES (?, ?, ?, ?, ?, ?)`,
    455                             [adminId, 'admin', 'admin@handcraft.com', adminPassword, 'admin', 1],
    456                             function(err) {
    457                                 if (err) {
    458                                     database.database.run('ROLLBACK');
    459                                     console.error('Error inserting admin user:', err.message);
    460                                     resolve();
    461                                     return;
    462                                 }
    463 
    464                                 // Insert into personal table (required for boss table)
    465                                 database.database.run(
    466                                     `INSERT INTO personal (id, first_name, last_name, ssn, email, password)
    467                                      VALUES (?, ?, ?, ?, ?, ?)`,
    468                                     [adminId, 'Admin', 'User', '0000000000000', 'admin@handcraft.com', adminPassword],
    469                                     function(err) {
    470                                         if (err) {
    471                                             database.database.run('ROLLBACK');
    472                                             console.error('Error inserting admin personal:', err.message);
    473                                             resolve();
    474                                             return;
    475                                         }
    476 
    477                                         // Insert into boss table (store owner)
    478                                         database.database.run(
    479                                             `INSERT INTO boss (boss_id, signature)
    480                                              VALUES (?, ?)`,
    481                                             [adminId, 'Admin Signature'],
    482                                             function(err) {
    483                                                 if (err) {
    484                                                     database.database.run('ROLLBACK');
    485                                                     console.error('Error inserting admin boss:', err.message);
    486                                                     resolve();
    487                                                     return;
    488                                                 }
    489 
    490                                                 // Insert into permissions
    491                                                 database.database.run(
    492                                                     `INSERT INTO permissions (personal_id, type, authorisation)
    493                                                      VALUES (?, ?, ?)`,
    494                                                     [adminId, 'ADMIN', 'full_access'],
    495                                                     function(err) {
    496                                                         if (err) {
    497                                                             console.error('Error inserting admin permissions:', err.message);
    498                                                             // Continue even if this fails
    499                                                         }
    500 
    501                                                         // Assign admin role
    502                                                         database.database.get(
    503                                                             'SELECT role_id FROM roles WHERE name = ?',
    504                                                             ['admin'],
    505                                                             (err, adminRole) => {
    506                                                                 if (!err && adminRole) {
    507                                                                     database.database.run(
    508                                                                         'INSERT OR IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)',
    509                                                                         [adminId, adminRole.role_id],
    510                                                                         (err) => {
    511                                                                             if (err) {
    512                                                                                 console.error('Error assigning admin role:', err.message);
    513                                                                             }
    514                                                                         }
    515                                                                     );
    516                                                                 }
    517 
    518                                                                 database.database.run('COMMIT', (commitErr) => {
    519                                                                     if (commitErr) {
    520                                                                         console.error('Error committing transaction:', commitErr);
    521                                                                         database.database.run('ROLLBACK');
    522                                                                     } else {
    523                                                                         console.log('\n');
    524                                                                         console.log('šŸ” ===== ADMIN CREDENTIALS =====');
    525                                                                         console.log('šŸ†” ID: 000000');
    526                                                                         console.log('šŸ‘¤ Username: admin');
    527                                                                         console.log('šŸ“§ Email: admin@handcraft.com');
    528                                                                         console.log('šŸ”‘ Password: Admin123!');
    529                                                                         console.log('āš ļø This is a first-time login. You will be required to change your password after 2FA verification.');
    530                                                                         console.log('================================\n');
    531                                                                     }
    532                                                                     resolve();
    533                                                                 });
    534                                                             }
    535                                                         );
    536                                                     }
    537                                                 );
    538                                             }
    539                                         );
    540                                     }
    541                                 );
    542                             }
    543                         );
     399                pool.connect().then(client => {
     400                    transactionClient = client;
     401                    return client.query('BEGIN');
     402                }).then(() => callback?.(null))
     403                    .catch(err => {
     404                        if (transactionClient) transactionClient.release();
     405                        transactionClient = null;
     406                        callback?.(err);
    544407                    });
    545                 } else {
    546                     console.log('āœ… Admin user already exists with ID:', existingAdmin.id);
    547                     resolve();
     408                return;
     409            }
     410
     411            if (normalized === 'COMMIT') {
     412                if (!transactionClient) {
     413                    callback?.(null);
     414                    return;
    548415                }
    549             }
    550         );
    551     });
    552 }
    553 
    554 function dropAllTables() {
    555     return new Promise((resolve, reject) => {
    556         console.log('šŸ—‘ļø Dropping all tables...');
    557 
    558         // Drop in reverse order of creation (respect foreign keys)
    559         const dropQueries = [
    560             'DROP TABLE IF EXISTS user_roles',
    561             'DROP TABLE IF EXISTS roles',
    562             'DROP TABLE IF EXISTS delivery_address',
    563             'DROP TABLE IF EXISTS image',
    564             'DROP TABLE IF EXISTS color',
    565             'DROP TABLE IF EXISTS audit_log',
    566             'DROP TABLE IF EXISTS report',
    567             'DROP TABLE IF EXISTS refund',
    568             'DROP TABLE IF EXISTS request',
    569             'DROP TABLE IF EXISTS review',
    570             'DROP TABLE IF EXISTS order_items',
    571             'DROP TABLE IF EXISTS "order"',
    572             'DROP TABLE IF EXISTS permissions',
    573             'DROP TABLE IF EXISTS works_in_store',
    574             'DROP TABLE IF EXISTS employees',
    575             'DROP TABLE IF EXISTS boss',
    576             'DROP TABLE IF EXISTS product',
    577             'DROP TABLE IF EXISTS personal',
    578             'DROP TABLE IF EXISTS users',
    579             'DROP TABLE IF EXISTS category',
    580             'DROP TABLE IF EXISTS store',
    581             'DROP TABLE IF EXISTS client'
    582         ];
    583 
    584         let index = 0;
    585 
    586         function runNext() {
    587             if (index >= dropQueries.length) {
    588                 console.log('āœ… All tables dropped');
    589                 resolve();
     416                const client = transactionClient;
     417                client.query('COMMIT')
     418                    .then(() => {
     419                        transactionClient = null;
     420                        client.release();
     421                        callback?.(null);
     422                    })
     423                    .catch(err => {
     424                        transactionClient = null;
     425                        client.release();
     426                        callback?.(err);
     427                    });
    590428                return;
    591429            }
    592430
    593             database.database.run(dropQueries[index], [], (err) => {
    594                 if (err) {
    595                     console.error(`Error dropping table: ${err.message}`);
    596                     // Continue anyway
     431            if (normalized === 'ROLLBACK') {
     432                if (!transactionClient) {
     433                    callback?.(null);
     434                    return;
    597435                }
    598                 index++;
    599                 runNext();
     436                const client = transactionClient;
     437                client.query('ROLLBACK')
     438                    .then(() => {
     439                        transactionClient = null;
     440                        client.release();
     441                        callback?.(null);
     442                    })
     443                    .catch(err => {
     444                        transactionClient = null;
     445                        client.release();
     446                        callback?.(err);
     447                    });
     448                return;
     449            }
     450
     451            dbQuery(sql, params || [], (err, result) => {
     452                if (callback) {
     453                    callback.call(
     454                        { changes: result ? result.rowCount : 0, lastID: result?.rows?.[0]?.id },
     455                        err
     456                    );
     457                }
    600458            });
    601459        }
    602 
    603         runNext();
    604     });
    605 }
    606 
    607 function createAllTables() {
    608     return new Promise((resolve, reject) => {
    609         console.log('šŸ—ļø Creating tables...');
    610 
    611         const createQueries = [
    612             // Client table (SERIAL ID starting from 1000)
    613             `CREATE TABLE IF NOT EXISTS client (
    614                 client_id INTEGER PRIMARY KEY AUTOINCREMENT,
    615                 first_name VARCHAR(100) NOT NULL,
    616                 last_name VARCHAR(100) NOT NULL,
    617                 email VARCHAR(255) UNIQUE NOT NULL,
    618                 password VARCHAR(255) NOT NULL,
    619                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    620             )`,
    621 
    622             // Store table (VARCHAR ID)
    623             `CREATE TABLE IF NOT EXISTS store (
    624                 store_id VARCHAR(10) PRIMARY KEY,
    625                 name VARCHAR(255) NOT NULL,
     460    },
     461
     462    async initializeDatabase() {
     463        // The database supplied by the project is authoritative.  Existing tables
     464        // are removed before recreation so an old incompatible schema can never
     465        // survive a restart and cause CREATE TABLE IF NOT EXISTS to skip columns.
     466        const schemaCompatibility = await pool.query(`
     467            SELECT
     468                EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name='product') AS product_exists,
     469                EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='product' AND column_name='category_id') AS product_has_category,
     470                EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='product' AND column_name='store_id') AS product_has_store,
     471                EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='order' AND column_name='store_id') AS order_has_store,
     472                EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='request' AND column_name='store_id') AS request_has_store,
     473                EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='review' AND column_name='review_id') AS review_has_review_id,
     474                EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='refund' AND column_name='request_date') AS refund_has_request_date
     475        `);
     476
     477        const c = schemaCompatibility.rows[0];
     478        const forceReset = String(process.env.RESET_DATABASE || '').toLowerCase() === 'true' || process.env.RESET_DATABASE === '1';
     479        const schemaMismatch = !c.product_exists || !c.product_has_category || c.product_has_store || c.order_has_store || c.request_has_store || c.review_has_review_id || c.refund_has_request_date;
     480        const resetDatabase = forceReset || schemaMismatch;
     481
     482        if (resetDatabase) {
     483            console.log('🧹 Existing PostgreSQL schema is incompatible with the project schema. Recreating project tables...');
     484            await pool.query(`
     485                DROP TABLE IF EXISTS audit_log CASCADE;
     486                DROP TABLE IF EXISTS user_roles CASCADE;
     487                DROP TABLE IF EXISTS roles CASCADE;
     488                DROP TABLE IF EXISTS users CASCADE;
     489                DROP TABLE IF EXISTS approves CASCADE;
     490                DROP TABLE IF EXISTS includes CASCADE;
     491                DROP TABLE IF EXISTS sells CASCADE;
     492                DROP TABLE IF EXISTS worked CASCADE;
     493                DROP TABLE IF EXISTS works_in_store CASCADE;
     494                DROP TABLE IF EXISTS makes_change CASCADE;
     495                DROP TABLE IF EXISTS "change" CASCADE;
     496                DROP TABLE IF EXISTS for_store CASCADE;
     497                DROP TABLE IF EXISTS answers CASCADE;
     498                DROP TABLE IF EXISTS makes_request CASCADE;
     499                DROP TABLE IF EXISTS request CASCADE;
     500                DROP TABLE IF EXISTS exchanges_data CASCADE;
     501                DROP TABLE IF EXISTS monthly_profit CASCADE;
     502                DROP TABLE IF EXISTS report CASCADE;
     503                DROP TABLE IF EXISTS refund CASCADE;
     504                DROP TABLE IF EXISTS review CASCADE;
     505                DROP TABLE IF EXISTS "order" CASCADE;
     506                DROP TABLE IF EXISTS delivery_address CASCADE;
     507                DROP TABLE IF EXISTS client CASCADE;
     508                DROP TABLE IF EXISTS employees CASCADE;
     509                DROP TABLE IF EXISTS boss CASCADE;
     510                DROP TABLE IF EXISTS permissions CASCADE;
     511                DROP TABLE IF EXISTS personal CASCADE;
     512                DROP TABLE IF EXISTS color CASCADE;
     513                DROP TABLE IF EXISTS image CASCADE;
     514                DROP TABLE IF EXISTS product CASCADE;
     515                DROP TABLE IF EXISTS store CASCADE;
     516                DROP TABLE IF EXISTS category CASCADE;
     517            `);
     518        }
     519
     520        const schema = `
     521            CREATE TABLE IF NOT EXISTS category (
     522                id SERIAL PRIMARY KEY,
     523                name VARCHAR(50) NOT NULL,
     524                parent_category_id INTEGER REFERENCES category(id) NOT NULL
     525            );
     526
     527            CREATE TABLE IF NOT EXISTS product (
     528                code VARCHAR(8) PRIMARY KEY DEFAULT '-1',
     529                price DECIMAL(10,2) NOT NULL CHECK (price >= 0.0),
     530                availability INTEGER NOT NULL,
     531                weight DECIMAL(5,2) NOT NULL CHECK (weight > 0),
     532                width_x_length_x_depth VARCHAR(20) NOT NULL,
     533                aprox_production_time INTEGER NOT NULL,
     534                description VARCHAR(500) NOT NULL,
     535                category_id INTEGER NOT NULL REFERENCES category(id) ON DELETE SET DEFAULT
     536            );
     537
     538            CREATE TABLE IF NOT EXISTS image (
     539                product_code VARCHAR(8) REFERENCES product(code) ON DELETE CASCADE,
     540                image VARCHAR NOT NULL DEFAULT 'Image NOT found!'
     541            );
     542
     543            CREATE TABLE IF NOT EXISTS color (
     544                product_code VARCHAR(8) REFERENCES product(code) ON DELETE CASCADE,
     545                color VARCHAR(50)
     546            );
     547
     548            CREATE TABLE IF NOT EXISTS store (
     549                store_ID VARCHAR(3) PRIMARY KEY,
     550                name VARCHAR(50) UNIQUE NOT NULL,
    626551                date_of_founding DATE NOT NULL,
    627                 physical_address TEXT NOT NULL,
    628                 store_email VARCHAR(255) UNIQUE NOT NULL,
    629                 rating DECIMAL(3,2) DEFAULT 0.0
    630             )`,
    631 
    632             // Category table (SERIAL ID starting from 1)
    633             `CREATE TABLE IF NOT EXISTS category (
    634                 category_id INTEGER PRIMARY KEY AUTOINCREMENT,
    635                 name VARCHAR(100) NOT NULL,
    636                 description TEXT,
    637                 parent_category_id INTEGER REFERENCES category(category_id) ON DELETE SET NULL
    638             )`,
    639 
    640             // Users table (VARCHAR ID)
    641             `CREATE TABLE IF NOT EXISTS users (
     552                physical_address VARCHAR(100) NOT NULL,
     553                store_email VARCHAR(40) UNIQUE NOT NULL CHECK (store_email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'),
     554                rating DECIMAL(2,1) NOT NULL DEFAULT 0 CHECK (rating>=0.0 AND rating<=5.0)
     555            );
     556
     557            CREATE TABLE IF NOT EXISTS personal (
     558                id VARCHAR(10) PRIMARY KEY,
     559                first_name VARCHAR(20) NOT NULL,
     560                last_name VARCHAR(20) NOT NULL,
     561                ssn VARCHAR(13) NOT NULL CHECK (ssn ~ '^[0-9]{13}$'),
     562                email VARCHAR(50) UNIQUE NOT NULL CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'),
     563                password VARCHAR NOT NULL
     564            );
     565
     566            CREATE TABLE IF NOT EXISTS permissions (
     567                personal_is VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE,
     568                type VARCHAR(50) NOT NULL,
     569                authorisation VARCHAR(50) NOT NULL
     570            );
     571
     572            CREATE TABLE IF NOT EXISTS boss (
     573                boss_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE
     574            );
     575
     576            CREATE TABLE IF NOT EXISTS employees (
     577                employee_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE,
     578                date_of_hire DATE NOT NULL
     579            );
     580
     581            CREATE TABLE IF NOT EXISTS client (
     582                client_ID SERIAL PRIMARY KEY,
     583                first_name VARCHAR(50) NOT NULL,
     584                last_name VARCHAR(50) NOT NULL,
     585                email VARCHAR(50) UNIQUE NOT NULL CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'),
     586                password VARCHAR NOT NULL
     587            );
     588
     589            CREATE TABLE IF NOT EXISTS delivery_address (
     590                client_ID INTEGER PRIMARY KEY REFERENCES client(client_ID) ON DELETE CASCADE,
     591                address VARCHAR(200) NOT NULL,
     592                city VARCHAR(30) NOT NULL,
     593                postcode VARCHAR(20) NOT NULL,
     594                country VARCHAR(40) NOT NULL,
     595                is_default BOOLEAN DEFAULT TRUE
     596            );
     597
     598            CREATE TABLE IF NOT EXISTS "order" (
     599                order_num VARCHAR(11) PRIMARY KEY,
     600                client_ID INTEGER REFERENCES client(client_ID) ON DELETE CASCADE,
     601                status VARCHAR(20) NOT NULL DEFAULT 'placed order',
     602                last_date_mod TIMESTAMP NOT NULL,
     603                payment_method VARCHAR(250) NOT NULL,
     604                discount DECIMAL(5,2) DEFAULT 0.0 CHECK(discount>=0.0 AND discount<=100.00),
     605                CONSTRAINT check_status CHECK (status IN ('placed order', 'being processed', 'shipping', 'delivered', 'canceled'))
     606            );
     607
     608            CREATE TABLE IF NOT EXISTS review (
     609                order_num VARCHAR(11) PRIMARY KEY REFERENCES "order"(order_num) ON DELETE CASCADE,
     610                comment VARCHAR(300),
     611                rating DECIMAL(2,1) NOT NULL CHECK(rating>=0.0 AND rating<=5.0),
     612                last_mod_date TIMESTAMP NOT NULL
     613            );
     614
     615            CREATE TABLE IF NOT EXISTS refund (
     616                refund_id SERIAL PRIMARY KEY,
     617                order_num VARCHAR(11) REFERENCES "order"(order_num) ON DELETE CASCADE,
     618                reason VARCHAR(300),
     619                amount DECIMAL(5,2) NOT NULL,
     620                status VARCHAR(100) NOT NULL DEFAULT 'requested refund',
     621                CONSTRAINT check_refund_status CHECK (status IN ('requested refund', 'being revied', 'approved', 'not approved', 'processed'))
     622            );
     623
     624            CREATE TABLE IF NOT EXISTS report (
     625                date TIMESTAMP NOT NULL,
     626                store_ID VARCHAR(3) NOT NULL REFERENCES store(store_ID) ON DELETE CASCADE,
     627                overall_profit NUMERIC NOT NULL DEFAULT 0.0 CHECK(overall_profit>=0),
     628                sales_trend VARCHAR(100) NOT NULL,
     629                marketing_growth VARCHAR(100) NOT NULL,
     630                owner_signature VARCHAR(50) NOT NULL DEFAULT 'Not signed yet',
     631                PRIMARY KEY (date, store_ID)
     632            );
     633
     634            CREATE TABLE IF NOT EXISTS monthly_profit (
     635                report_date TIMESTAMP NOT NULL,
     636                store_ID VARCHAR(3) NOT NULL,
     637                month_and_year DATE NOT NULL,
     638                profit NUMERIC NOT NULL DEFAULT 0.0,
     639                PRIMARY KEY(report_date, store_ID),
     640                FOREIGN KEY (report_date, store_ID) REFERENCES report(date, store_ID) ON DELETE CASCADE
     641            );
     642
     643            CREATE TABLE IF NOT EXISTS exchanges_data (
     644                report_date TIMESTAMP NOT NULL,
     645                store_ID VARCHAR(3) NOT NULL,
     646                monthly_profit NUMERIC NOT NULL DEFAULT 0.0,
     647                date TIMESTAMP NOT NULL,
     648                sales NUMERIC NOT NULL DEFAULT 0.0,
     649                damages NUMERIC NOT NULL DEFAULT 0.0 CHECK (damages<=0),
     650                PRIMARY KEY (report_date, store_ID),
     651                FOREIGN KEY (report_date, store_ID) REFERENCES report(date, store_ID) ON DELETE CASCADE
     652            );
     653
     654            CREATE TABLE IF NOT EXISTS request (
     655                request_num VARCHAR(14) PRIMARY KEY,
     656                date_and_time TIMESTAMP NOT NULL,
     657                problem VARCHAR(300) NOT NULL,
     658                notes_of_communication VARCHAR,
     659                customer_satisfaction NUMERIC NOT NULL
     660            );
     661
     662            CREATE TABLE IF NOT EXISTS makes_request (
     663                client_ID INTEGER NOT NULL REFERENCES client(client_ID) ON DELETE CASCADE,
     664                order_num VARCHAR(11) UNIQUE NOT NULL REFERENCES "order"(order_num) ON DELETE CASCADE,
     665                PRIMARY KEY(client_ID, order_num)
     666            );
     667
     668            CREATE TABLE IF NOT EXISTS answers (
     669                request_num VARCHAR(14) REFERENCES request(request_num) ON DELETE CASCADE,
     670                personal_id VARCHAR(10) NOT NULL REFERENCES personal(id) ON DELETE CASCADE,
     671                PRIMARY KEY(request_num, personal_id)
     672            );
     673
     674            CREATE TABLE IF NOT EXISTS for_store (
     675                request_num VARCHAR(14) REFERENCES request(request_num) ON DELETE CASCADE,
     676                store_ID VARCHAR(3) REFERENCES store(store_ID) ON DELETE CASCADE,
     677                PRIMARY KEY(request_num, store_ID)
     678            );
     679
     680            CREATE TABLE IF NOT EXISTS "change" (
     681                date_and_time TIMESTAMP NOT NULL,
     682                product_code VARCHAR(8) REFERENCES product(code) ON DELETE CASCADE,
     683                changes VARCHAR NOT NULL,
     684                PRIMARY KEY (date_and_time, product_code)
     685            );
     686
     687            CREATE TABLE IF NOT EXISTS makes_change (
     688                personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE,
     689                change_date_time TIMESTAMP,
     690                product_code VARCHAR(8),
     691                PRIMARY KEY(personal_id, change_date_time, product_code),
     692                FOREIGN KEY(change_date_time, product_code) REFERENCES "change"(date_and_time, product_code) ON DELETE CASCADE
     693            );
     694
     695            CREATE TABLE IF NOT EXISTS works_in_store (
     696                personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE,
     697                store_ID VARCHAR(3) REFERENCES store(store_ID) ON DELETE CASCADE,
     698                PRIMARY KEY(personal_id, store_ID)
     699            );
     700
     701            CREATE TABLE IF NOT EXISTS worked (
     702                personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE,
     703                report_date TIMESTAMP,
     704                store_ID VARCHAR(3),
     705                wage NUMERIC NOT NULL CHECK (wage>=0),
     706                pay_method VARCHAR DEFAULT 'full-time',
     707                total_hours NUMERIC NOT NULL,
     708                week VARCHAR(23) NOT NULL,
     709                PRIMARY KEY (personal_id, report_date, store_ID),
     710                FOREIGN KEY (report_date, store_ID) REFERENCES report(date, store_ID) ON DELETE CASCADE,
     711                CONSTRAINT check_pay_method CHECK (pay_method IN ('full_time', 'part-time', 'custom'))
     712            );
     713
     714            CREATE TABLE IF NOT EXISTS sells (
     715                product_code VARCHAR(8) REFERENCES product(code) ON DELETE CASCADE,
     716                store_ID VARCHAR(3) REFERENCES store(store_ID) ON DELETE CASCADE,
     717                discount NUMERIC NOT NULL DEFAULT 0.0,
     718                PRIMARY KEY (product_code, store_ID)
     719            );
     720
     721            CREATE TABLE IF NOT EXISTS includes (
     722                order_num VARCHAR(11) REFERENCES "order"(order_num) ON DELETE CASCADE,
     723                product_code VARCHAR(8) REFERENCES product(code) ON DELETE CASCADE,
     724                quantity INTEGER NOT NULL CHECK(quantity>=0),
     725                PRIMARY KEY (order_num, product_code)
     726            );
     727
     728            CREATE TABLE IF NOT EXISTS approves (
     729                boss_id VARCHAR(10) REFERENCES boss(boss_id) ON DELETE CASCADE,
     730                report_date TIMESTAMP,
     731                store_ID VARCHAR(3),
     732                owner_signature VARCHAR NOT NULL,
     733                PRIMARY KEY (boss_id, report_date, store_ID),
     734                FOREIGN KEY (report_date, store_ID) REFERENCES report(date, store_ID) ON DELETE CASCADE
     735            );
     736
     737            -- These four small tables are application authentication/audit storage.
     738            -- They do not modify any of the project tables above.
     739            CREATE TABLE IF NOT EXISTS users (
    642740                id VARCHAR(50) PRIMARY KEY,
    643741                username VARCHAR(100) UNIQUE NOT NULL,
    … …  
    645743                password VARCHAR(255) NOT NULL,
    646744                user_type VARCHAR(50) NOT NULL,
    647                 force_password_change INTEGER DEFAULT 0,
     745                force_password_change BOOLEAN DEFAULT FALSE,
    648746                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    649             )`,
    650 
    651             // Personal table (VARCHAR ID - format: storeId(3) + '001' for owner, storeId(3) + employeeNum(3) for employees)
    652             `CREATE TABLE IF NOT EXISTS personal (
    653                 id VARCHAR(10) PRIMARY KEY,
    654                 first_name VARCHAR(100) NOT NULL,
    655                 last_name VARCHAR(100) NOT NULL,
    656                 ssn VARCHAR(13) UNIQUE NOT NULL,
    657                 email VARCHAR(255) UNIQUE NOT NULL,
    658                 password VARCHAR(255) NOT NULL,
    659                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    660             )`,
    661 
    662             // Product table (VARCHAR ID)
    663             `CREATE TABLE IF NOT EXISTS product (
    664                 id VARCHAR(50) PRIMARY KEY,
    665                 code VARCHAR(20) UNIQUE NOT NULL,
    666                 description TEXT NOT NULL,
    667                 price DECIMAL(10,2) NOT NULL,
    668                 availability INTEGER NOT NULL DEFAULT 0,
    669                 weight DECIMAL(10,2),
    670                 dimensions VARCHAR(50),
    671                 production_time INTEGER,
    672                 category_id INTEGER REFERENCES category(category_id) ON DELETE SET NULL,
    673                 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,
    674                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    675             )`,
    676 
    677             // Boss table (VARCHAR ID - references personal.id)
    678             `CREATE TABLE IF NOT EXISTS boss (
    679                 boss_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE,
    680                 signature TEXT NOT NULL,
    681                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    682             )`,
    683 
    684             // Employees table (VARCHAR ID - references personal.id)
    685             `CREATE TABLE IF NOT EXISTS employees (
    686                 employee_id VARCHAR(10) PRIMARY KEY REFERENCES personal(id) ON DELETE CASCADE,
    687                 date_of_hire DATE NOT NULL,
    688                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    689             )`,
    690 
    691             // Works_in_store table (junction)
    692             `CREATE TABLE IF NOT EXISTS works_in_store (
    693                 personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE,
    694                 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,
    695                 PRIMARY KEY (personal_id, store_id)
    696             )`,
    697 
    698             // Permissions table
    699             `CREATE TABLE IF NOT EXISTS permissions (
    700                 permission_id INTEGER PRIMARY KEY AUTOINCREMENT,
    701                 personal_id VARCHAR(10) REFERENCES personal(id) ON DELETE CASCADE,
    702                 type VARCHAR(50) NOT NULL,
    703                 authorisation TEXT,
    704                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    705             )`,
    706 
    707             // Order table (VARCHAR ID)
    708             `CREATE TABLE IF NOT EXISTS "order" (
    709                 order_num VARCHAR(20) PRIMARY KEY,
    710                 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL,
    711                 order_date TIMESTAMP NOT NULL,
    712                 quantity INTEGER NOT NULL,
    713                 payment_method VARCHAR(50) NOT NULL,
    714                 discount DECIMAL(10,2) DEFAULT 0,
    715                 delivery_address TEXT NOT NULL,
    716                 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE SET NULL,
    717                 status VARCHAR(50) DEFAULT 'pending',
    718                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    719             )`,
    720 
    721             // Order_items table
    722             `CREATE TABLE IF NOT EXISTS order_items (
    723                 item_id INTEGER PRIMARY KEY AUTOINCREMENT,
    724                 order_num VARCHAR(20) REFERENCES "order"(order_num) ON DELETE CASCADE,
    725                 product_code VARCHAR(20) REFERENCES product(code) ON DELETE SET NULL,
    726                 quantity INTEGER NOT NULL,
    727                 price DECIMAL(10,2) NOT NULL,
    728                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    729             )`,
    730 
    731             // Review table (VARCHAR ID)
    732             `CREATE TABLE IF NOT EXISTS review (
    733                 review_id VARCHAR(20) PRIMARY KEY,
    734                 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL,
    735                 product_code VARCHAR(20) REFERENCES product(code) ON DELETE CASCADE,
    736                 rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
    737                 comment TEXT,
    738                 review_date TIMESTAMP NOT NULL,
    739                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    740             )`,
    741 
    742             // Request table (VARCHAR ID)
    743             `CREATE TABLE IF NOT EXISTS request (
    744                 request_num VARCHAR(50) PRIMARY KEY,
    745                 date_and_time TIMESTAMP NOT NULL,
    746                 problem TEXT NOT NULL,
    747                 client_id INTEGER REFERENCES client(client_id) ON DELETE SET NULL,
    748                 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,
    749                 status VARCHAR(50) DEFAULT 'pending',
    750                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    751             )`,
    752 
    753             // Refund table (VARCHAR ID)
    754             `CREATE TABLE IF NOT EXISTS refund (
    755                 refund_id VARCHAR(50) PRIMARY KEY,
    756                 order_num VARCHAR(20) REFERENCES "order"(order_num) ON DELETE CASCADE,
    757                 amount DECIMAL(10,2) NOT NULL,
    758                 reason TEXT NOT NULL,
    759                 status VARCHAR(50) DEFAULT 'pending',
    760                 request_date TIMESTAMP NOT NULL,
    761                 processed_date TIMESTAMP,
    762                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    763             )`,
    764 
    765             // Report table (VARCHAR ID)
    766             `CREATE TABLE IF NOT EXISTS report (
    767                 id VARCHAR(50) PRIMARY KEY,
    768                 store_id VARCHAR(10) REFERENCES store(store_id) ON DELETE CASCADE,
    769                 period VARCHAR(50) NOT NULL,
    770                 start_date DATE NOT NULL,
    771                 end_date DATE NOT NULL,
    772                 type VARCHAR(50) NOT NULL,
    773                 generated_by VARCHAR(10) REFERENCES personal(id) ON DELETE SET NULL,
    774                 generated_at TIMESTAMP NOT NULL,
    775                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    776             )`,
    777 
    778             // Audit_log table (SERIAL ID)
    779             `CREATE TABLE IF NOT EXISTS audit_log (
    780                 log_id INTEGER PRIMARY KEY AUTOINCREMENT,
     747            );
     748
     749            CREATE TABLE IF NOT EXISTS roles (
     750                role_id SERIAL PRIMARY KEY,
     751                name VARCHAR(50) UNIQUE NOT NULL,
     752                description TEXT
     753            );
     754
     755            CREATE TABLE IF NOT EXISTS user_roles (
     756                user_id VARCHAR(50) REFERENCES users(id) ON DELETE CASCADE,
     757                role_id INTEGER REFERENCES roles(role_id) ON DELETE CASCADE,
     758                PRIMARY KEY(user_id, role_id)
     759            );
     760
     761            CREATE TABLE IF NOT EXISTS audit_log (
     762                log_id BIGSERIAL PRIMARY KEY,
    781763                user_id VARCHAR(50),
    782764                action VARCHAR(100) NOT NULL,
    … …  
    786768                ip_address VARCHAR(45),
    787769                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    788             )`,
    789 
    790             // Color table (SERIAL ID)
    791             `CREATE TABLE IF NOT EXISTS color (
    792                 color_id INTEGER PRIMARY KEY AUTOINCREMENT,
    793                 name VARCHAR(50) NOT NULL,
    794                 hex_code VARCHAR(7) NOT NULL,
    795                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    796             )`,
    797 
    798             // Image table (SERIAL ID)
    799             `CREATE TABLE IF NOT EXISTS image (
    800                 image_id INTEGER PRIMARY KEY AUTOINCREMENT,
    801                 product_code VARCHAR(20) REFERENCES product(code) ON DELETE CASCADE,
    802                 image_url TEXT NOT NULL,
    803                 is_primary BOOLEAN DEFAULT FALSE,
    804                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    805             )`,
    806 
    807             // Delivery_address table (SERIAL ID)
    808             `CREATE TABLE IF NOT EXISTS delivery_address (
    809                 address_id INTEGER PRIMARY KEY AUTOINCREMENT,
    810                 client_id INTEGER REFERENCES client(client_id) ON DELETE CASCADE,
    811                 address TEXT NOT NULL,
    812                 city VARCHAR(100) NOT NULL,
    813                 postcode VARCHAR(20) NOT NULL,
    814                 country VARCHAR(100) NOT NULL,
    815                 is_default BOOLEAN DEFAULT FALSE,
    816                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    817             )`,
    818 
    819             // Roles table (SERIAL ID)
    820             `CREATE TABLE IF NOT EXISTS roles (
    821                 role_id INTEGER PRIMARY KEY AUTOINCREMENT,
    822                 name VARCHAR(50) UNIQUE NOT NULL,
    823                 description TEXT,
    824                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    825             )`,
    826 
    827             // User_roles table (junction)
    828             `CREATE TABLE IF NOT EXISTS user_roles (
    829                 user_id VARCHAR(50) REFERENCES users(id) ON DELETE CASCADE,
    830                 role_id INTEGER REFERENCES roles(role_id) ON DELETE CASCADE,
    831                 PRIMARY KEY (user_id, role_id)
    832             )`
     770            );
     771
     772            CREATE INDEX IF NOT EXISTS idx_product_category ON product(category_id);
     773            CREATE INDEX IF NOT EXISTS idx_order_client ON "order"(client_ID);
     774            CREATE INDEX IF NOT EXISTS idx_review_order ON review(order_num);
     775            CREATE INDEX IF NOT EXISTS idx_request_date ON request(date_and_time);
     776            CREATE INDEX IF NOT EXISTS idx_refund_order ON refund(order_num);
     777            CREATE INDEX IF NOT EXISTS idx_personal_email ON personal(email);
     778            CREATE INDEX IF NOT EXISTS idx_client_email ON client(email);
     779            CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
     780            CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
     781            CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log(user_id);
     782        `;
     783
     784        await pool.query(schema);
     785
     786        const roles = [
     787            ['admin', 'System administrator'],
     788            ['store_owner', 'Store owner'],
     789            ['store_employee', 'Store employee'],
     790            ['client', 'Registered client'],
     791            ['guest', 'Unregistered guest']
    833792        ];
    834793
    835         let index = 0;
    836 
    837         function runNext() {
    838             if (index >= createQueries.length) {
    839                 console.log('āœ… All tables created');
    840                 resolve();
    841                 return;
    842             }
    843 
    844             const tableName = createQueries[index].split('TABLE')[1].split('(')[0].trim().replace('IF NOT EXISTS', '').trim();
    845             console.log(`Creating table: ${tableName}...`);
    846 
    847             database.database.run(createQueries[index], [], (err) => {
    848                 if (err) {
    849                     console.error(`Error creating table: ${err.message}`);
    850                     reject(err);
    851                     return;
    852                 }
    853                 console.log(`āœ… Created table: ${tableName}`);
    854                 index++;
    855                 runNext();
    856             });
     794        for (const [name, description] of roles) {
     795            await pool.query(
     796                'INSERT INTO roles(name, description) VALUES($1,$2) ON CONFLICT(name) DO NOTHING',
     797                [name, description]
     798            );
    857799        }
    858800
    859         runNext();
    860     });
    861 }
    862 
    863 function createIndexes() {
    864     return new Promise((resolve, reject) => {
    865         console.log('šŸ“Š Creating indexes...');
    866 
    867         const indexQueries = [
    868             'CREATE INDEX IF NOT EXISTS idx_product_store ON product(store_id)',
    869             'CREATE INDEX IF NOT EXISTS idx_product_category ON product(category_id)',
    870             'CREATE INDEX IF NOT EXISTS idx_order_client ON "order"(client_id)',
    871             'CREATE INDEX IF NOT EXISTS idx_order_store ON "order"(store_id)',
    872             'CREATE INDEX IF NOT EXISTS idx_order_date ON "order"(order_date)',
    873             'CREATE INDEX IF NOT EXISTS idx_review_client ON review(client_id)',
    874             'CREATE INDEX IF NOT EXISTS idx_review_product ON review(product_code)',
    875             'CREATE INDEX IF NOT EXISTS idx_request_client ON request(client_id)',
    876             'CREATE INDEX IF NOT EXISTS idx_request_store ON request(store_id)',
    877             'CREATE INDEX IF NOT EXISTS idx_refund_order ON refund(order_num)',
    878             'CREATE INDEX IF NOT EXISTS idx_refund_status ON refund(status)',
    879             'CREATE INDEX IF NOT EXISTS idx_personal_email ON personal(email)',
    880             'CREATE INDEX IF NOT EXISTS idx_client_email ON client(email)',
    881             'CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)',
    882             'CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)',
    883             'CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log(user_id)',
    884             'CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action)',
    885             'CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log(created_at)',
    886             'CREATE INDEX IF NOT EXISTS idx_delivery_client ON delivery_address(client_id)',
    887             'CREATE INDEX IF NOT EXISTS idx_works_in_store_personal ON works_in_store(personal_id)',
    888             'CREATE INDEX IF NOT EXISTS idx_works_in_store_store ON works_in_store(store_id)'
     801        const hash = bcrypt.hashSync('Admin123!', 10);
     802        await pool.query(
     803            `INSERT INTO users(id, username, email, password, user_type, force_password_change)
     804             VALUES('000000','admin','admin@handcraft.com',$1,'admin',TRUE) ON CONFLICT(id) DO NOTHING`,
     805            [hash]
     806        );
     807        await pool.query(
     808            `INSERT INTO personal(id, first_name, last_name, ssn, email, password)
     809             VALUES('000000','Admin','User','0000000000000','admin@handcraft.com',$1) ON CONFLICT(id) DO NOTHING`,
     810            [hash]
     811        );
     812        await pool.query(`INSERT INTO boss(boss_id) VALUES('000000') ON CONFLICT(boss_id) DO NOTHING`);
     813        await pool.query(
     814            `INSERT INTO permissions(personal_is,type,authorisation)
     815             VALUES('000000','ADMIN','full_access') ON CONFLICT(personal_is) DO NOTHING`
     816        );
     817        await pool.query(
     818            `INSERT INTO user_roles(user_id,role_id)
     819             SELECT '000000', role_id FROM roles WHERE name='admin' ON CONFLICT DO NOTHING`
     820        );
     821        console.log('āœ… PostgreSQL project schema was recreated successfully');
     822    },
     823
     824    close() {
     825        return pool.end();
     826    },
     827
     828    getUserById(id, callback) {
     829        dbQuery(
     830            `SELECT u.*,
     831                    COALESCE(json_agg(json_build_object('name',r.name,'description',r.description))
     832                             FILTER (WHERE r.role_id IS NOT NULL), '[]') AS roles
     833             FROM users u
     834             LEFT JOIN user_roles ur ON ur.user_id=u.id
     835             LEFT JOIN roles r ON r.role_id=ur.role_id
     836             WHERE u.id=$1
     837             GROUP BY u.id`,
     838            [String(id)],
     839            (err, result) => callback(err, result?.rows?.[0])
     840        );
     841    },
     842
     843    getUserByUsername(username, callback) {
     844        dbQuery(
     845            `SELECT u.*,
     846                    COALESCE(json_agg(json_build_object('name',r.name,'description',r.description))
     847                             FILTER (WHERE r.role_id IS NOT NULL), '[]') AS roles
     848             FROM users u
     849             LEFT JOIN user_roles ur ON ur.user_id=u.id
     850             LEFT JOIN roles r ON r.role_id=ur.role_id
     851             WHERE u.username=$1 OR u.email=$1
     852             GROUP BY u.id
     853             LIMIT 1`,
     854            [username],
     855            (err, result) => callback(err, result?.rows?.[0])
     856        );
     857    },
     858
     859    createUser(id, username, email, password, userType, callback) {
     860        dbQuery(
     861            `INSERT INTO users(id,username,email,password,user_type,force_password_change)
     862             VALUES($1,$2,$3,$4,$5,FALSE) RETURNING id`,
     863            [String(id), username, email, password, userType],
     864            (err, result) => {
     865                if (err) return callback(err);
     866                const roleName = userType === 'client' ? 'client' :
     867                    userType === 'store_owner' ? 'store_owner' :
     868                        userType === 'store_employee' ? 'store_employee' : 'guest';
     869                dbQuery(
     870                    `INSERT INTO user_roles(user_id,role_id)
     871                     SELECT $1, role_id FROM roles WHERE name=$2`,
     872                    [String(id), roleName],
     873                    roleErr => callback(roleErr, String(id))
     874                );
     875            }
     876        );
     877    },
     878
     879    createClient(data, callback) {
     880        dbQuery(
     881            `INSERT INTO client(first_name,last_name,email,password)
     882             VALUES($1,$2,$3,$4) RETURNING client_id`,
     883            [data.firstName || data.first_name || '', data.lastName || data.last_name || '', data.email, data.password],
     884            (err, result) => callback(err, result?.rows?.[0]?.client_id)
     885        );
     886    },
     887
     888    getClientByEmail(email, callback) {
     889        dbQuery('SELECT * FROM client WHERE email=$1', [email],
     890            (err, result) => callback(err, result?.rows?.[0]));
     891    },
     892
     893    getClientById(id, callback) {
     894        dbQuery('SELECT * FROM client WHERE client_id=$1', [id],
     895            (err, result) => callback(err, result?.rows?.[0]));
     896    },
     897
     898    getPersonalByEmail(email, callback) {
     899        dbQuery('SELECT * FROM personal WHERE email=$1', [email],
     900            (err, result) => callback(err, result?.rows?.[0]));
     901    },
     902
     903    getPersonalById(id, callback) {
     904        dbQuery('SELECT * FROM personal WHERE id=$1', [String(id)],
     905            (err, result) => callback(err, result?.rows?.[0]));
     906    },
     907
     908    verifyPassword(password, hash) {
     909        try { return bcrypt.compareSync(password, hash); } catch { return false; }
     910    },
     911
     912    verifyClientPassword(password, hash, callback) {
     913        bcrypt.compare(password, hash, callback);
     914    },
     915
     916    logAudit(userId, action, resourceType, resourceId, details, ipAddress) {
     917        dbQuery(
     918            `INSERT INTO audit_log(user_id,action,resource_type,resource_id,details,ip_address)
     919             VALUES($1,$2,$3,$4,$5,$6)`,
     920            [userId == null ? null : String(userId), action, resourceType,
     921                resourceId == null ? null : String(resourceId), details, ipAddress],
     922            () => {}
     923        );
     924    },
     925
     926    getProducts(categoryId, searchTerm, callback) {
     927        const params = [];
     928        const where = [];
     929        if (categoryId) {
     930            params.push(categoryId);
     931            where.push(`p.category_id=$${params.length}`);
     932        }
     933        if (searchTerm) {
     934            params.push(`%${searchTerm}%`);
     935            where.push(`(p.description ILIKE $${params.length} OR p.code ILIKE $${params.length})`);
     936        }
     937        const sql = `SELECT p.*, c.name AS category_name, LEFT(p.code,3) AS store_id
     938                     FROM product p
     939                     LEFT JOIN category c ON c.id=p.category_id
     940                     ${where.length ? 'WHERE ' + where.join(' AND ') : ''}
     941                     ORDER BY p.code`;
     942        dbQuery(sql, params, (err, result) => callback(err, result?.rows || []));
     943    },
     944
     945    getProductById(id, callback) {
     946        dbQuery(
     947            `SELECT p.*, c.name AS category_name, LEFT(p.code,3) AS store_id
     948             FROM product p
     949             LEFT JOIN category c ON c.id=p.category_id
     950             WHERE p.code=$1 LIMIT 1`,
     951            [String(id)],
     952            (err,result)=>callback(err,result?.rows?.[0])
     953        );
     954    },
     955
     956    getProductByCode(code, callback) {
     957        dbQuery(
     958            `SELECT p.*, c.name AS category_name, LEFT(p.code,3) AS store_id
     959             FROM product p
     960             LEFT JOIN category c ON c.id=p.category_id
     961             WHERE p.code=$1`,
     962            [code],
     963            (err,result)=>callback(err,result?.rows?.[0])
     964        );
     965    },
     966
     967    addProduct(personalId, data, callback) {
     968        const storeId = data.store_id || data.storeId || String(data.code || '').slice(0,3);
     969        if (!data.category_id) {
     970            return callback(new Error('category_id is required because product.category_id is NOT NULL'));
     971        }
     972        dbQuery(
     973            `INSERT INTO product(code,price,availability,weight,width_x_length_x_depth,
     974                                 aprox_production_time,description,category_id)
     975             VALUES($1,$2,$3,$4,$5,$6,$7,$8) RETURNING code`,
     976            [
     977                data.code, data.price, data.availability ?? 0, data.weight,
     978                data.width_x_length_x_depth || data.dimensions || '',
     979                data.aprox_production_time ?? data.production_time ?? 0,
     980                data.description, data.category_id
     981            ],
     982            (err,result)=>{
     983                if (err) return callback(err);
     984                dbQuery(
     985                    `INSERT INTO sells(product_code,store_ID,discount)
     986                     VALUES($1,$2,$3)
     987                     ON CONFLICT(product_code,store_ID)
     988                     DO UPDATE SET discount=EXCLUDED.discount`,
     989                    [data.code,storeId,data.discount || 0],
     990                    e => callback(e, data.code)
     991                );
     992            }
     993        );
     994    },
     995
     996    updateProduct(personalId, data, callback) {
     997        const fields = [];
     998        const params = [];
     999        const allowed = [
     1000            ['price','price'], ['availability','availability'], ['weight','weight'],
     1001            ['width_x_length_x_depth','width_x_length_x_depth'],
     1002            ['dimensions','width_x_length_x_depth'],
     1003            ['aprox_production_time','aprox_production_time'],
     1004            ['production_time','aprox_production_time'],
     1005            ['description','description'], ['category_id','category_id']
    8891006        ];
    890 
    891         let index = 0;
    892 
    893         function runNext() {
    894             if (index >= indexQueries.length) {
    895                 console.log('āœ… Indexes created');
    896                 resolve();
    897                 return;
    898             }
    899 
    900             database.database.run(indexQueries[index], [], (err) => {
    901                 if (err) {
    902                     console.log(`āš ļø Index creation warning for ${indexQueries[index].substring(0, 50)}...: ${err.message}`);
    903                 }
    904                 index++;
    905                 runNext();
    906             });
     1007        for (const [input,col] of allowed) {
     1008            if (data[input] !== undefined) {
     1009                params.push(data[input]);
     1010                fields.push(`${col}=$${params.length}`);
     1011            }
    9071012        }
    908 
    909         runNext();
    910     });
    911 }
    912 
    913 function insertInitialData() {
    914     return new Promise((resolve, reject) => {
    915         console.log('šŸ“ Inserting initial data...');
    916 
    917         // Insert default roles
    918         const roles = [
    919             { name: 'admin', description: 'System administrator' },
    920             { name: 'store_owner', description: 'Store owner' },
    921             { name: 'store_employee', description: 'Store employee' },
    922             { name: 'client', description: 'Registered client' },
    923             { name: 'guest', description: 'Unregistered guest' }
    924         ];
    925 
    926         let rolesInserted = 0;
    927 
    928         roles.forEach(role => {
    929             database.database.run(
    930                 `INSERT INTO roles (name, description)
    931                  VALUES (?, ?)
    932                  ON CONFLICT DO NOTHING`,
    933                 [role.name, role.description],
    934                 (err) => {
    935                     if (err) {
    936                         console.error(`Error inserting role ${role.name}:`, err.message);
    937                     }
    938                     rolesInserted++;
    939 
    940                     if (rolesInserted === roles.length) {
    941                         console.log('āœ… Roles inserted');
    942                         // Create admin user with ID 000000
    943                         createAdminUser();
    944 
    945                         // Ensure General category exists
    946                         database.ensureGeneralCategory((err) => {
    947                             if (err) {
    948                                 console.error('Error ensuring General category:', err.message);
    949                             } else {
    950                                 console.log('āœ… General category checked/created');
    951                             }
    952                             resolve();
    953                         });
    954                     }
    955                 }
    956             );
    957         });
    958     });
    959 }
    960 
    961 // Function to create admin user with ID 000000
    962 function createAdminUser() {
    963     const adminId = '000000';
    964     const adminPassword = bcrypt.hashSync('Admin123!', 10);
    965 
    966     database.database.get(
    967         'SELECT * FROM users WHERE id = ? OR username = ? OR email = ?',
    968         [adminId, 'admin', 'admin@handcraft.com'],
    969         (err, existingAdmin) => {
    970             if (err) {
    971                 console.error('Error checking for existing admin:', err.message);
    972                 return;
    973             }
    974 
    975             if (!existingAdmin) {
    976                 // Start a transaction
    977                 database.database.run('BEGIN TRANSACTION', (err) => {
    978                     if (err) {
    979                         console.error('Error beginning transaction:', err);
    980                         return;
    981                     }
    982 
    983                     // Insert into users table
    984                     database.database.run(
    985                         `INSERT INTO users (id, username, email, password, user_type, force_password_change)
    986                          VALUES (?, ?, ?, ?, ?, ?)`,
    987                         [adminId, 'admin', 'admin@handcraft.com', adminPassword, 'admin', 1],
    988                         function(err) {
    989                             if (err) {
    990                                 database.database.run('ROLLBACK');
    991                                 console.error('Error inserting admin user:', err.message);
    992                                 return;
    993                             }
    994 
    995                             // Insert into personal table (required for boss table)
    996                             database.database.run(
    997                                 `INSERT INTO personal (id, first_name, last_name, ssn, email, password)
    998                                  VALUES (?, ?, ?, ?, ?, ?)`,
    999                                 [adminId, 'Admin', 'User', '0000000000000', 'admin@handcraft.com', adminPassword],
    1000                                 function(err) {
    1001                                     if (err) {
    1002                                         database.database.run('ROLLBACK');
    1003                                         console.error('Error inserting admin personal:', err.message);
    1004                                         return;
    1005                                     }
    1006 
    1007                                     // Insert into boss table (store owner)
    1008                                     database.database.run(
    1009                                         `INSERT INTO boss (boss_id, signature)
    1010                                          VALUES (?, ?)`,
    1011                                         [adminId, 'Admin Signature'],
    1012                                         function(err) {
    1013                                             if (err) {
    1014                                                 database.database.run('ROLLBACK');
    1015                                                 console.error('Error inserting admin boss:', err.message);
    1016                                                 return;
    1017                                             }
    1018 
    1019                                             // Insert into permissions
    1020                                             database.database.run(
    1021                                                 `INSERT INTO permissions (personal_id, type, authorisation)
    1022                                                  VALUES (?, ?, ?)`,
    1023                                                 [adminId, 'ADMIN', 'full_access'],
    1024                                                 function(err) {
    1025                                                     if (err) {
    1026                                                         console.error('Error inserting admin permissions:', err.message);
    1027                                                         // Continue even if this fails
    1028                                                     }
    1029 
    1030                                                     // Assign admin role
    1031                                                     database.database.get(
    1032                                                         'SELECT role_id FROM roles WHERE name = ?',
    1033                                                         ['admin'],
    1034                                                         (err, adminRole) => {
    1035                                                             if (!err && adminRole) {
    1036                                                                 database.database.run(
    1037                                                                     'INSERT OR IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)',
    1038                                                                     [adminId, adminRole.role_id],
    1039                                                                     (err) => {
    1040                                                                         if (err) {
    1041                                                                             console.error('Error assigning admin role:', err.message);
    1042                                                                         }
    1043                                                                     }
    1044                                                                 );
    1045                                                             }
    1046 
    1047                                                             database.database.run('COMMIT', (commitErr) => {
    1048                                                                 if (commitErr) {
    1049                                                                     console.error('Error committing transaction:', commitErr);
    1050                                                                     database.database.run('ROLLBACK');
    1051                                                                 } else {
    1052                                                                     console.log('\n');
    1053                                                                     console.log('šŸ” ===== ADMIN CREDENTIALS =====');
    1054                                                                     console.log('šŸ†” ID: 000000');
    1055                                                                     console.log('šŸ‘¤ Username: admin');
    1056                                                                     console.log('šŸ“§ Email: admin@handcraft.com');
    1057                                                                     console.log('šŸ”‘ Password: Admin123!');
    1058                                                                     console.log('āš ļø This is a first-time login. You will be required to change your password after 2FA verification.');
    1059                                                                     console.log('================================\n');
    1060                                                                 }
    1061                                                             });
    1062                                                         }
    1063                                                     );
    1064                                                 }
    1065                                             );
    1066                                         }
    1067                                     );
    1068                                 }
     1013        if (!fields.length) return callback(null,0);
     1014        params.push(data.code);
     1015        dbQuery(`UPDATE product SET ${fields.join(', ')} WHERE code=$${params.length}`, params,
     1016            (err,result)=>callback(err,result?.rowCount || 0));
     1017    },
     1018
     1019    deleteProduct(productCode, storeId, personalId, callback) {
     1020        dbQuery('DELETE FROM product WHERE code=$1 AND LEFT(code,3)=$2', [productCode,storeId],
     1021            (err)=>callback(err));
     1022    },
     1023
     1024    createCategory(data, callback) {
     1025        const parent = data.parent_category_id ?? data.parentCategoryId ?? data.parent_id;
     1026        if (parent === undefined || parent === null || parent === '') {
     1027            return callback(new Error('parent_category_id is required by the project schema'));
     1028        }
     1029        dbQuery(
     1030            `INSERT INTO category(name,parent_category_id) VALUES($1,$2) RETURNING id,name,parent_category_id`,
     1031            [data.name, parent],
     1032            (err,result)=>callback(err,result?.rows?.[0])
     1033        );
     1034    },
     1035
     1036    getCategories(callback) {
     1037        dbQuery('SELECT * FROM category ORDER BY name', [], (err,result)=>callback(err,result?.rows||[]));
     1038    },
     1039
     1040    getCategoriesWithParents(callback) {
     1041        dbQuery(
     1042            `SELECT c.*,p.name AS parent_name
     1043             FROM category c LEFT JOIN category p ON p.id=c.parent_category_id
     1044             ORDER BY c.name`,
     1045            [], (err,result)=>callback(err,result?.rows||[])
     1046        );
     1047    },
     1048
     1049    getStores(callback) {
     1050        dbQuery('SELECT * FROM store ORDER BY name', [], (err,result)=>callback(err,result?.rows||[]));
     1051    },
     1052
     1053    createOrderNew(data, callback) {
     1054        const items = data.items || data.products || data.order_items || [];
     1055        const storeId = data.store_id || data.storeId || (items[0]?.code ? String(items[0].code).slice(0,3) : null);
     1056        if (!storeId) return callback(new Error('Store ID is required'));
     1057        const year = String(new Date().getFullYear()).slice(-3);
     1058
     1059        dbQuery(
     1060            `SELECT COUNT(*)::int AS n
     1061             FROM "order"
     1062             WHERE LEFT(order_num,3)=$1
     1063               AND SUBSTRING(order_num FROM 4 FOR 3)=$2`,
     1064            [storeId, year],
     1065            (countErr,countResult)=>{
     1066                if (countErr) return callback(countErr);
     1067                const seq=Number(countResult.rows[0].n)+1;
     1068                const orderNum=`${storeId}${year}${String(seq).padStart(5,'0')}`;
     1069                dbQuery(
     1070                    `INSERT INTO "order"(order_num,client_ID,status,last_date_mod,payment_method,discount)
     1071                     VALUES($1,$2,$3,CURRENT_TIMESTAMP,$4,$5) RETURNING order_num`,
     1072                    [orderNum,data.client_id,data.status||'placed order',data.payment_method||'cash',data.discount||0],
     1073                    (err,result)=>{
     1074                        if(err) return callback(err);
     1075                        let pending=items.length;
     1076                        if(!pending) return callback(null,orderNum);
     1077                        let firstErr=null;
     1078                        for(const item of items){
     1079                            dbQuery(
     1080                                `INSERT INTO includes(order_num,product_code,quantity) VALUES($1,$2,$3)`,
     1081                                [orderNum,item.product_code||item.code,item.quantity||1],
     1082                                e=>{ if(e && !firstErr) firstErr=e; if(--pending===0) callback(firstErr,firstErr?undefined:orderNum); }
    10691083                            );
    10701084                        }
    1071                     );
    1072                 });
    1073             } else {
    1074                 console.log('āœ… Admin user already exists with ID:', existingAdmin.id);
    1075             }
    1076         }
    1077     );
    1078 }
    1079 
    1080 // Initialize database on startup
    1081 (async function() {
     1085                    }
     1086                );
     1087            }
     1088        );
     1089    },
     1090
     1091    getOrdersByClient(clientId, callback) {
     1092        dbQuery(
     1093            `SELECT o.*, LEFT(o.order_num,3) AS store_id,
     1094                    o.last_date_mod AS order_date,
     1095                    COALESCE(json_agg(json_build_object('product_code',i.product_code,'quantity',i.quantity,'price',p.price))
     1096                             FILTER(WHERE i.product_code IS NOT NULL),'[]') AS items
     1097             FROM "order" o
     1098             LEFT JOIN includes i ON i.order_num=o.order_num
     1099             LEFT JOIN product p ON p.code=i.product_code
     1100             WHERE o.client_ID=$1
     1101             GROUP BY o.order_num
     1102             ORDER BY o.last_date_mod DESC`,
     1103            [clientId],(err,result)=>callback(err,result?.rows||[])
     1104        );
     1105    },
     1106
     1107    createReviewNew(data, callback) {
     1108        dbQuery(
     1109            `INSERT INTO review(order_num,comment,rating,last_mod_date)
     1110             VALUES($1,$2,$3,CURRENT_TIMESTAMP)
     1111             RETURNING order_num`,
     1112            [data.order_num,data.comment||null,data.rating],
     1113            (err,result)=>callback(err,result?.rows?.[0]?.order_num)
     1114        );
     1115    },
     1116
     1117    createRequest(data, callback) {
     1118        dbQuery(
     1119            `INSERT INTO request(request_num,date_and_time,problem,notes_of_communication,customer_satisfaction)
     1120             VALUES($1,$2,$3,$4,0) RETURNING request_num`,
     1121            [data.request_num,data.date_and_time,data.problem,data.notes_of_communication||null],
     1122            (err,result)=>{
     1123                if (err) return callback(err);
     1124                dbQuery(
     1125                    `INSERT INTO for_store(request_num,store_ID) VALUES($1,$2)`,
     1126                    [data.request_num,data.store_id],
     1127                    storeErr=>{
     1128                        if (storeErr) return callback(storeErr);
     1129                        if (data.order_num) {
     1130                            dbQuery(
     1131                                `INSERT INTO makes_request(client_ID,order_num) VALUES($1,$2)`,
     1132                                [data.client_id,data.order_num],
     1133                                e=>callback(e,data.request_num)
     1134                            );
     1135                        } else {
     1136                            callback(null,data.request_num);
     1137                        }
     1138                    }
     1139                );
     1140            }
     1141        );
     1142    },
     1143
     1144    createRefund(data, callback) {
     1145        const suppliedId = data.refund_id;
     1146        const query = suppliedId
     1147            ? `INSERT INTO refund(refund_id,order_num,reason,amount,status) VALUES($1,$2,$3,$4,$5) RETURNING refund_id`
     1148            : `INSERT INTO refund(order_num,reason,amount,status) VALUES($1,$2,$3,$4) RETURNING refund_id`;
     1149        const params = suppliedId
     1150            ? [Number.parseInt(String(suppliedId),10) || undefined,data.order_num,data.reason||null,data.amount,data.status||'requested refund']
     1151            : [data.order_num,data.reason||null,data.amount,data.status||'requested refund'];
     1152        dbQuery(query,params,(err,result)=>callback(err,result?.rows?.[0]?.refund_id));
     1153    },
     1154
     1155    getAllUsers(callback) {
     1156        dbQuery(`SELECT u.*,COALESCE(json_agg(r.name) FILTER(WHERE r.role_id IS NOT NULL),'[]') roles
     1157                 FROM users u LEFT JOIN user_roles ur ON ur.user_id=u.id
     1158                              LEFT JOIN roles r ON r.role_id=ur.role_id GROUP BY u.id ORDER BY u.created_at DESC`,
     1159            [],(err,result)=>callback(err,result?.rows||[]));
     1160    },
     1161
     1162    getAllOrders(callback) {
     1163        dbQuery(`SELECT o.*, LEFT(o.order_num,3) AS store_id, o.last_date_mod AS order_date,
     1164                        c.first_name,c.last_name,c.email
     1165                 FROM "order" o
     1166                 LEFT JOIN client c ON c.client_id=o.client_ID
     1167                 ORDER BY o.last_date_mod DESC`,
     1168            [],(err,result)=>callback(err,result?.rows||[]));
     1169    },
     1170
     1171    getStoreProducts(storeId, callback) {
     1172        dbQuery(`SELECT p.*,c.name AS category_name,LEFT(p.code,3) AS store_id,s.discount
     1173                 FROM product p
     1174                 LEFT JOIN category c ON c.id=p.category_id
     1175                 LEFT JOIN sells s ON s.product_code=p.code AND s.store_ID=$1
     1176                 WHERE LEFT(p.code,3)=$1 OR EXISTS(SELECT 1 FROM sells sx WHERE sx.product_code=p.code AND sx.store_ID=$1)
     1177                 ORDER BY p.code`,[storeId],(err,result)=>callback(err,result?.rows||[]));
     1178    },
     1179
     1180    getStoreOrders(storeId, callback) {
     1181        dbQuery(`SELECT o.*,LEFT(o.order_num,3) AS store_id,o.last_date_mod AS order_date,c.first_name,c.last_name
     1182                 FROM "order" o
     1183                 LEFT JOIN client c ON c.client_id=o.client_ID
     1184                 WHERE LEFT(o.order_num,3)=$1 ORDER BY o.last_date_mod DESC`,[storeId],
     1185            (err,result)=>callback(err,result?.rows||[]));
     1186    },
     1187
     1188    getStoreEmployees(storeId, callback) {
     1189        dbQuery(`SELECT p.*,e.date_of_hire,per.type,per.authorisation
     1190                 FROM personal p JOIN works_in_store w ON w.personal_id=p.id
     1191                 LEFT JOIN employees e ON e.employee_id=p.id
     1192                 LEFT JOIN permissions per ON per.personal_is=p.id
     1193                 WHERE w.store_ID=$1 ORDER BY p.last_name,p.first_name`,
     1194            [storeId],(err,result)=>callback(err,result?.rows||[]));
     1195    },
     1196
     1197    getStoreReports(storeId, callback) {
     1198        dbQuery(`SELECT * FROM report WHERE store_ID=$1 ORDER BY date DESC`,[storeId],
     1199            (err,result)=>callback(err,result?.rows||[]));
     1200    },
     1201
     1202    getStoreStats(storeId, callback) {
     1203        const sql=`SELECT
     1204            (SELECT COUNT(*) FROM product WHERE LEFT(code,3)=$1)::int AS product_count,
     1205            (SELECT COUNT(*) FROM "order" WHERE LEFT(order_num,3)=$1)::int AS order_count,
     1206            (SELECT COALESCE(SUM(i.quantity*p.price*(1-o.discount/100)),0)
     1207             FROM "order" o JOIN includes i ON i.order_num=o.order_num JOIN product p ON p.code=i.product_code
     1208             WHERE LEFT(o.order_num,3)=$1) AS revenue,
     1209            (SELECT COUNT(*) FROM works_in_store WHERE store_ID=$1)::int AS employee_count,
     1210            (SELECT COUNT(*) FROM for_store WHERE store_ID=$1)::int AS request_count,
     1211            (SELECT COUNT(*) FROM refund r JOIN "order" o ON o.order_num=r.order_num WHERE LEFT(o.order_num,3)=$1)::int AS refund_count`;
     1212        dbQuery(sql,[storeId],(err,result)=>callback(err,result?.rows?.[0]||{}));
     1213    },
     1214
     1215    getEmployeeTasks(personalId, storeId, callback) {
     1216        dbQuery(`SELECT r.*,a.personal_id AS answered_by
     1217                 FROM request r
     1218                 JOIN for_store fs ON fs.request_num=r.request_num
     1219                 LEFT JOIN answers a ON a.request_num=r.request_num
     1220                 WHERE fs.store_ID=$1 AND (a.personal_id=$2 OR a.personal_id IS NULL)
     1221                 ORDER BY r.date_and_time DESC`,
     1222            [storeId,personalId],(err,result)=>callback(err,result?.rows||[]));
     1223    },
     1224
     1225    getClientStats(clientId, callback) {
     1226        dbQuery(`SELECT
     1227            (SELECT COUNT(*) FROM "order" WHERE client_ID=$1)::int AS order_count,
     1228            (SELECT COUNT(*) FROM review r JOIN "order" o ON o.order_num=r.order_num WHERE o.client_ID=$1)::int AS review_count,
     1229            (SELECT COUNT(*) FROM makes_request WHERE client_ID=$1)::int AS request_count,
     1230            (SELECT COUNT(*) FROM refund r JOIN "order" o ON o.order_num=r.order_num WHERE o.client_ID=$1)::int AS refund_count`,
     1231            [clientId],(err,result)=>callback(err,result?.rows?.[0]||{}));
     1232    }
     1233};
     1234
     1235
     1236
     1237// PostgreSQL schema initialization.
     1238// The schema is based on the supplied Pasted markdown.md. A few invalid PostgreSQL
     1239// declarations in the original paste are corrected here (for example DECIMMAL,
     1240// PARTIAL KEY, and composite-key column-name typos). The existing HTTP layer also
     1241// uses only the project schema plus the four authentication/audit support tables.
     1242(async () => {
    10821243    try {
    1083         await initializeDatabase();
     1244        await database.initializeDatabase();
    10841245        console.log('āœ… Database initialization completed');
    10851246    } catch (err) {
    10861247        console.error('āŒ Database initialization failed:', err);
     1248        process.exitCode = 1;
    10871249    }
    10881250})();
    … …  
    11581320
    11591321                database.database.get(
    1160                     'SELECT boss_id FROM boss WHERE boss_id = ?',
     1322                    'SELECT boss_id FROM boss WHERE boss_id = $1',
    11611323                    [personalId],
    11621324                    (err, boss) => {
    … …  
    13801542
    13811543                database.database.get(
    1382                     'SELECT store_id FROM store WHERE store_email = ?',
     1544                    'SELECT store_id FROM store WHERE store_email = $1',
    13831545                    [formData.storeEmail],
    13841546                    (err, existingStore) => {
    … …  
    17601922                    // Insert into store table (store_id is VARCHAR)
    17611923                    database.database.run(
    1762                         'INSERT INTO store (store_id, name, date_of_founding, physical_address, store_email, rating) VALUES (?, ?, ?, ?, ?, ?)',
     1924                        'INSERT INTO store (store_id, name, date_of_founding, physical_address, store_email, rating) VALUES ($1, $2, $3, $4, $5, $6)',
    17631925                        [
    17641926                            tempStoreData.storeId,
    … …  
    17801942                            // Insert into personal table (id is VARCHAR)
    17811943                            database.database.run(
    1782                                 'INSERT INTO personal (id, first_name, last_name, ssn, email, password) VALUES (?, ?, ?, ?, ?, ?)',
     1944                                'INSERT INTO personal (id, first_name, last_name, ssn, email, password) VALUES ($1, $2, $3, $4, $5, $6)',
    17831945                                [
    17841946                                    tempStoreData.personalId,
    … …  
    18091971                                    // Insert into boss table (boss_id is VARCHAR, references personal.id)
    18101972                                    database.database.run(
    1811                                         'INSERT INTO boss (boss_id, signature) VALUES (?, ?)',
    1812                                         [tempStoreData.personalId, tempStoreData.signature],
     1973                                        'INSERT INTO boss (boss_id) VALUES ($1)',
     1974                                        [tempStoreData.personalId],
    18131975                                        (err) => {
    18141976                                            if (err) {
    … …  
    18221984                                            // Insert into works_in_store table (personal_id is VARCHAR, store_id is VARCHAR)
    18231985                                            database.database.run(
    1824                                                 'INSERT INTO works_in_store (personal_id, store_id) VALUES (?, ?)',
     1986                                                'INSERT INTO works_in_store (personal_id, store_id) VALUES ($1, $2)',
    18251987                                                [tempStoreData.personalId, tempStoreData.storeId],
    18261988                                                (err) => {
    … …  
    18351997                                                    // Insert into permissions table (personal_id is VARCHAR)
    18361998                                                    database.database.run(
    1837                                                         'INSERT INTO permissions (personal_id, type, authorisation) VALUES (?, ?, ?)',
     1999                                                        'INSERT INTO permissions (personal_id, type, authorisation) VALUES ($1, $2, $3)',
    18382000                                                        [tempStoreData.personalId, 'BOSS', 'full_access'],
    18392001                                                        (err) => {
    … …  
    18442006                                                            // Also create entry in users table for login with force_password_change = 1
    18452007                                                            database.database.run(
    1846                                                                 'INSERT INTO users (id, username, email, password, user_type, force_password_change) VALUES (?, ?, ?, ?, ?, ?)',
     2008                                                                'INSERT INTO users (id, username, email, password, user_type, force_password_change) VALUES ($1, $2, $3, $4, $5, $6)',
    18472009                                                                [
    18482010                                                                    tempStoreData.personalId,
    … …  
    19372099                        if (tempUserData.address && tempUserData.city && tempUserData.postcode && tempUserData.country) {
    19382100                            database.database.run(
    1939                                 'INSERT INTO delivery_address (client_id, address, city, postcode, country, is_default) VALUES (?, ?, ?, ?, ?, ?)',
     2101                                'INSERT INTO delivery_address (client_id, address, city, postcode, country, is_default) VALUES ($1, $2, $3, $4, $5, $6)',
    19402102                                [
    19412103                                    clientId,
    … …  
    21582320                            // Check if this is a boss (store owner)
    21592321                            database.database.get(
    2160                                 'SELECT boss_id FROM boss WHERE boss_id = ?',
     2322                                'SELECT boss_id FROM boss WHERE boss_id = $1',
    21612323                                [personal.id],
    21622324                                (err, boss) => {
    … …  
    21692331                                        // Check if first time login from users table
    21702332                                        database.database.get(
    2171                                             'SELECT force_password_change FROM users WHERE email = ?',
     2333                                            'SELECT force_password_change FROM users WHERE email = $1',
    21722334                                            [email],
    21732335                                            (err, user) => {
    … …  
    22192381                                    // Check if this is an employee
    22202382                                    database.database.get(
    2221                                         'SELECT employee_id FROM employees WHERE employee_id = ?',
     2383                                        'SELECT employee_id FROM employees WHERE employee_id = $1',
    22222384                                        [personal.id],
    22232385                                        (err, employee) => {
    … …  
    22292391                                                // This is an employee
    22302392                                                database.database.get(
    2231                                                     'SELECT force_password_change FROM users WHERE email = ?',
     2393                                                    'SELECT force_password_change FROM users WHERE email = $1',
    22322394                                                    [email],
    22332395                                                    (err, user) => {
    … …  
    22802442                                            // Treat as regular user
    22812443                                            database.database.get(
    2282                                                 'SELECT * FROM users WHERE email = ?',
     2444                                                'SELECT * FROM users WHERE email = $1',
    22832445                                                [email],
    22842446                                                (err, user) => {
    … …  
    24222584
    24232585            database.database.get(
    2424                 'SELECT * FROM users WHERE email = ?',
     2586                'SELECT * FROM users WHERE email = $1',
    24252587                [email],
    24262588                (err, user) => {
    … …  
    26732835                                // Personal user (store owner/employee)
    26742836                                database.database.get(
    2675                                     'SELECT boss_id FROM boss WHERE boss_id = ?',
     2837                                    'SELECT boss_id FROM boss WHERE boss_id = $1',
    26762838                                    [userId],
    26772839                                    (err, boss) => {
    … …  
    27742936
    27752937                    database.database.get(
    2776                         'SELECT boss_id FROM boss WHERE boss_id = ?',
     2938                        'SELECT boss_id FROM boss WHERE boss_id = $1',
    27772939                        [personalId],
    27782940                        (err, boss) => {
    … …  
    27842946                                database.database.all(
    27852947                                    `SELECT s.* FROM store s
    2786                                      JOIN works_in_store w ON s.store_id = w.store_id
    2787                                      WHERE w.personal_id = ?`,
     2948                                                         JOIN works_in_store w ON s.store_id = w.store_id
     2949                                     WHERE w.personal_id = $1`,
    27882950                                    [personalId],
    27892951                                    (err, stores) => {
    … …  
    28092971                            } else {
    28102972                                database.database.get(
    2811                                     'SELECT employee_id FROM employees WHERE employee_id = ?',
     2973                                    'SELECT employee_id FROM employees WHERE employee_id = $1',
    28122974                                    [personalId],
    28132975                                    (err, employee) => {
    … …  
    28192981                                            database.database.all(
    28202982                                                `SELECT s.* FROM store s
    2821                                                  JOIN works_in_store w ON s.store_id = w.store_id
    2822                                                  WHERE w.personal_id = ?`,
     2983                                                                     JOIN works_in_store w ON s.store_id = w.store_id
     2984                                                 WHERE w.personal_id = $1`,
    28232985                                                [personalId],
    28242986                                                (err, stores) => {
    … …  
    29443106                                id: category.id,
    29453107                                name: category.name,
    2946                                 parent_id: category.parent_id,
     3108                                parent_id: category.parent_category_id,
    29473109                                description: category.description
    29483110                            }
    … …  
    30103172
    30113173                    database.database.get(
    3012                         'SELECT COUNT(*) as order_count FROM "order" WHERE store_id = ? AND strftime("%Y", order_date) = ?',
     3174                        'SELECT COUNT(*) AS order_count FROM "order" WHERE LEFT(order_num,3) = $1 AND SUBSTRING(order_num FROM 4 FOR 3)::INTEGER = ($2::INTEGER % 1000)',
    30133175                        [storeId, new Date().getFullYear().toString()],
    30143176                        (err, result) => {
    … …  
    31413303
    31423304                    database.database.get(
    3143                         'SELECT COUNT(*) as request_count FROM request WHERE store_id = ? AND strftime("%Y", date_and_time) = ? AND strftime("%m", date_and_time) = ?',
     3305                        'SELECT COUNT(*)::int AS request_count FROM request r JOIN for_store fs ON fs.request_num=r.request_num WHERE fs.store_ID = $1 AND EXTRACT(YEAR FROM r.date_and_time)::int = $2::int AND EXTRACT(MONTH FROM r.date_and_time)::int = $3::int',
    31443306                        [storeId, now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0')],
    31453307                        (err, result) => {
    … …  
    31993361
    32003362                    database.database.get(
    3201                         'SELECT store_id FROM "order" WHERE order_num = ?',
     3363                        'SELECT LEFT(order_num,3) AS store_id FROM "order" WHERE order_num = $1',
    32023364                        [refundData.order_num],
    32033365                        (err, result) => {
    … …  
    32143376
    32153377                            database.database.get(
    3216                                 'SELECT COUNT(*) as refund_count FROM refund WHERE strftime("%Y", request_date) = ? AND strftime("%m", request_date) = ?',
     3378                                'SELECT COUNT(*)::int AS refund_count FROM refund WHERE SUBSTRING(refund_id::text FROM 4 FOR 2)::int = $2::int AND SUBSTRING(refund_id::text FROM 6 FOR 3)::int = ($1::int % 1000)',
    32173379                                [now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0')],
    32183380                                (err, result) => {
    … …  
    32643426
    32653427                database.database.get(
    3266                     'SELECT store_id FROM works_in_store WHERE personal_id = ?',
     3428                    'SELECT store_id FROM works_in_store WHERE personal_id = $1',
    32673429                    [personalId],
    32683430                    (err, bossStore) => {
    … …  
    32823444
    32833445                        database.database.get(
    3284                             'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     3446                            'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    32853447                            [personalId, storeId],
    32863448                            (err, ownsStore) => {
    … …  
    32913453                                }
    32923454
    3293                                 // FIXED: Changed SQL syntax from SUBSTRING(code FROM 4) to SUBSTR(code, 4) for SQLite compatibility
     3455                                // PostgreSQL: extract the numeric product sequence from the store-prefixed product code
    32943456                                database.database.get(
    3295                                     'SELECT MAX(CAST(SUBSTR(code, 4) AS INTEGER)) as max_product_num FROM product WHERE store_id = ?',
     3457                                    'SELECT MAX(CAST(SUBSTRING(code FROM 4) AS INTEGER)) AS max_product_num FROM product WHERE LEFT(code,3) = $1',
    32963458                                    [storeId],
    32973459                                    (err, result) => {
    … …  
    33623524
    33633525                database.database.get(
    3364                     'SELECT store_id FROM product WHERE code = ?',
     3526                    'SELECT LEFT(code,3) AS store_id FROM product WHERE code = $1',
    33653527                    [productData.code],
    33663528                    (err, product) => {
    … …  
    33723534
    33733535                        database.database.get(
    3374                             'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     3536                            'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    33753537                            [personalId, product.store_id],
    33763538                            (err, ownsStore) => {
    … …  
    34993661
    35003662                        database.database.run(
    3501                             'UPDATE users SET password = ?, force_password_change = 0 WHERE id = ?',
     3663                            'UPDATE users SET password = $1, force_password_change = FALSE WHERE id = $2',
    35023664                            [hashedPassword, userId],
    35033665                            function(err) {
    … …  
    35113673                                // Also update password in personal table if it exists (for admin)
    35123674                                database.database.run(
    3513                                     'UPDATE personal SET password = ? WHERE id = ?',
     3675                                    'UPDATE personal SET password = $1 WHERE id = $2',
    35143676                                    [hashedPassword, userId],
    35153677                                    function(err) {
    … …  
    35853747
    35863748                                database.database.run(
    3587                                     'UPDATE personal SET password = ? WHERE id = ?',
     3749                                    'UPDATE personal SET password = $1 WHERE id = $2',
    35883750                                    [hashedPassword, userId],
    35893751                                    function(err) {
    … …  
    35973759                                        // Also update in users table if exists
    35983760                                        database.database.run(
    3599                                             'UPDATE users SET password = ?, force_password_change = 0 WHERE email = ?',
     3761                                            'UPDATE users SET password = $1, force_password_change = FALSE WHERE email = $2',
    36003762                                            [hashedPassword, personal.email],
    36013763                                            function(err) {
    … …  
    36083770                                        // Determine user type (boss/owner or employee)
    36093771                                        database.database.get(
    3610                                             'SELECT boss_id FROM boss WHERE boss_id = ?',
     3772                                            'SELECT boss_id FROM boss WHERE boss_id = $1',
    36113773                                            [userId],
    36123774                                            (err, boss) => {
    … …  
    36843846
    36853847            database.database.get(
    3686                 'SELECT boss_id FROM boss WHERE boss_id = ?',
     3848                'SELECT boss_id FROM boss WHERE boss_id = $1',
    36873849                [personalId],
    36883850                (err, boss) => {
    … …  
    37903952
    37913953                                        database.database.run(
    3792                                             'INSERT INTO personal (id, first_name, last_name, ssn, email, password) VALUES (?, ?, ?, ?, ?, ?)',
     3954                                            'INSERT INTO personal (id, first_name, last_name, ssn, email, password) VALUES ($1, $2, $3, $4, $5, $6)',
    37933955                                            [
    37943956                                                newPersonalId,
    … …  
    38183980
    38193981                                                database.database.run(
    3820                                                     'INSERT INTO employees (employee_id, date_of_hire) VALUES (?, ?)',
     3982                                                    'INSERT INTO employees (employee_id, date_of_hire) VALUES ($1, $2)',
    38213983                                                    [newPersonalId, dateOfHire],
    38223984                                                    (err) => {
    … …  
    38303992
    38313993                                                        database.database.run(
    3832                                                             'INSERT INTO works_in_store (personal_id, store_id) VALUES (?, ?)',
     3994                                                            'INSERT INTO works_in_store (personal_id, store_id) VALUES ($1, $2)',
    38333995                                                            [newPersonalId, storeId],
    38343996                                                            (err) => {
    … …  
    38424004
    38434005                                                                database.database.run(
    3844                                                                     'INSERT INTO permissions (personal_id, type, authorisation) VALUES (?, ?, ?)',
     4006                                                                    'INSERT INTO permissions (personal_id, type, authorisation) VALUES ($1, $2, $3)',
    38454007                                                                    [newPersonalId, 'EMPLOYEE', 'limited_access'],
    38464008                                                                    (err) => {
    … …  
    38514013                                                                        // Also create entry in users table for login with force_password_change = 1
    38524014                                                                        database.database.run(
    3853                                                                             'INSERT INTO users (id, username, email, password, user_type, force_password_change) VALUES (?, ?, ?, ?, ?, ?)',
     4015                                                                            'INSERT INTO users (id, username, email, password, user_type, force_password_change) VALUES ($1, $2, $3, $4, $5, $6)',
    38544016                                                                            [
    38554017                                                                                newPersonalId,
    … …  
    39244086
    39254087            database.database.get(
    3926                 'SELECT boss_id FROM boss WHERE boss_id = ?',
     4088                'SELECT boss_id FROM boss WHERE boss_id = $1',
    39274089                [personalId],
    39284090                (err, boss) => {
    … …  
    39474109
    39484110                        database.database.get(
    3949                             'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4111                            'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    39504112                            [personalId, storeId],
    39514113                            (err, bossStore) => {
    … …  
    39574119
    39584120                                database.database.get(
    3959                                     'SELECT personal_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4121                                    'SELECT personal_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    39604122                                    [employeeId, storeId],
    39614123                                    (err, employeeStore) => {
    … …  
    39674129
    39684130                                        database.database.get(
    3969                                             'SELECT boss_id FROM boss WHERE boss_id = ?',
     4131                                            'SELECT boss_id FROM boss WHERE boss_id = $1',
    39704132                                            [employeeId],
    39714133                                            (err, isBoss) => {
    … …  
    39894151
    39904152                                                    database.database.run(
    3991                                                         'DELETE FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4153                                                        'DELETE FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    39924154                                                        [employeeId, storeId],
    39934155                                                        (err) => {
    … …  
    40014163
    40024164                                                            database.database.run(
    4003                                                                 'DELETE FROM employees WHERE employee_id = ?',
     4165                                                                'DELETE FROM employees WHERE employee_id = $1',
    40044166                                                                [employeeId],
    40054167                                                                (err) => {
    … …  
    40094171
    40104172                                                                    database.database.run(
    4011                                                                         'DELETE FROM permissions WHERE personal_id = ?',
     4173                                                                        'DELETE FROM permissions WHERE personal_id = $1',
    40124174                                                                        [employeeId],
    40134175                                                                        (err) => {
    … …  
    40174179
    40184180                                                                            database.database.run(
    4019                                                                                 'DELETE FROM personal WHERE id = ?',
     4181                                                                                'DELETE FROM personal WHERE id = $1',
    40204182                                                                                [employeeId],
    40214183                                                                                (err) => {
    … …  
    40264188                                                                                    // Also delete from users table
    40274189                                                                                    database.database.run(
    4028                                                                                         'DELETE FROM users WHERE id = ?',
     4190                                                                                        'DELETE FROM users WHERE id = $1',
    40294191                                                                                        [employeeId],
    40304192                                                                                        (err) => {
    … …  
    40934255
    40944256            database.database.get(
    4095                 'SELECT boss_id FROM boss WHERE boss_id = ?',
     4257                'SELECT boss_id FROM boss WHERE boss_id = $1',
    40964258                [personalId],
    40974259                (err, boss) => {
    … …  
    41164278
    41174279                        database.database.get(
    4118                             'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4280                            'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    41194281                            [personalId, storeId],
    41204282                            (err, bossStore) => {
    … …  
    41264288
    41274289                                database.database.get(
    4128                                     'SELECT personal_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4290                                    'SELECT personal_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    41294291                                    [employeeId, storeId],
    41304292                                    (err, employeeStore) => {
    … …  
    41504312
    41514313                                        database.database.run(
    4152                                             'UPDATE permissions SET type = ?, authorisation = ? WHERE personal_id = ?',
     4314                                            'UPDATE permissions SET type = $1, authorisation = $2 WHERE personal_id = $3',
    41534315                                            [permissionType, authorization, employeeId],
    41544316                                            function(err) {
    … …  
    41994361
    42004362            database.database.get(
    4201                 'SELECT boss_id FROM boss WHERE boss_id = ?',
     4363                'SELECT boss_id FROM boss WHERE boss_id = $1',
    42024364                [personalId],
    42034365                (err, boss) => {
    … …  
    42224384
    42234385                        database.database.get(
    4224                             'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4386                            'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    42254387                            [personalId, storeId],
    42264388                            (err, bossStore) => {
    … …  
    42324394
    42334395                                database.database.get(
    4234                                     'SELECT personal_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4396                                    'SELECT personal_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    42354397                                    [employeeId, storeId],
    42364398                                    (err, employeeStore) => {
    … …  
    42454407
    42464408                                        if (firstName) {
    4247                                             updates.push('first_name = ?');
     4409                                            updates.push(`first_name = $${params.length + 1}`);
    42484410                                            params.push(firstName);
    42494411                                        }
    42504412
    42514413                                        if (lastName) {
    4252                                             updates.push('last_name = ?');
     4414                                            updates.push(`last_name = $${params.length + 1}`);
    42534415                                            params.push(lastName);
    42544416                                        }
    … …  
    42604422                                                return;
    42614423                                            }
    4262                                             updates.push('email = ?');
     4424                                            updates.push(`email = $${params.length + 1}`);
    42634425                                            params.push(email);
    42644426                                        }
    … …  
    42734435
    42744436                                        database.database.run(
    4275                                             `UPDATE personal SET ${updates.join(', ')} WHERE id = ?`,
     4437                                            `UPDATE personal SET ${updates.join(', ')} WHERE id = $${params.length}`,
    42764438                                            params,
    42774439                                            function(err) {
    … …  
    42864448                                                if (email) {
    42874449                                                    database.database.run(
    4288                                                         'UPDATE users SET email = ? WHERE id = ?',
     4450                                                        'UPDATE users SET email = $1 WHERE id = $2',
    42894451                                                        [email, employeeId],
    42904452                                                        (err) => {
    … …  
    42984460                                                if (firstName || lastName) {
    42994461                                                    database.database.get(
    4300                                                         'SELECT first_name, last_name FROM personal WHERE id = ?',
     4462                                                        'SELECT first_name, last_name FROM personal WHERE id = $1',
    43014463                                                        [employeeId],
    43024464                                                        (err, personal) => {
    … …  
    43044466                                                                const newUsername = `${personal.first_name} ${personal.last_name}`;
    43054467                                                                database.database.run(
    4306                                                                     'UPDATE users SET username = ? WHERE id = ?',
     4468                                                                    'UPDATE users SET username = $1 WHERE id = $2',
    43074469                                                                    [newUsername, employeeId],
    43084470                                                                    (err) => {
    … …  
    43424504            if (!storeId) {
    43434505                database.database.get(
    4344                     'SELECT store_id FROM works_in_store WHERE personal_id = ? LIMIT 1',
     4506                    'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
    43454507                    [personalId],
    43464508                    (err, store) => {
    … …  
    43674529
    43684530            database.database.get(
    4369                 'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4531                'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    43704532                [personalId, storeId],
    43714533                (err, ownsStore) => {
    … …  
    43964558            if (!storeId) {
    43974559                database.database.get(
    4398                     'SELECT store_id FROM works_in_store WHERE personal_id = ? LIMIT 1',
     4560                    'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
    43994561                    [personalId],
    44004562                    (err, store) => {
    … …  
    44214583
    44224584            database.database.get(
    4423                 'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4585                'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    44244586                [personalId, storeId],
    44254587                (err, ownsStore) => {
    … …  
    44504612            if (!storeId) {
    44514613                database.database.get(
    4452                     'SELECT store_id FROM works_in_store WHERE personal_id = ? LIMIT 1',
     4614                    'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
    44534615                    [personalId],
    44544616                    (err, store) => {
    … …  
    44754637
    44764638            database.database.get(
    4477                 'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4639                'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    44784640                [personalId, storeId],
    44794641                (err, ownsStore) => {
    … …  
    45044666            if (!storeId) {
    45054667                database.database.get(
    4506                     'SELECT store_id FROM works_in_store WHERE personal_id = ? LIMIT 1',
     4668                    'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
    45074669                    [personalId],
    45084670                    (err, store) => {
    … …  
    45294691
    45304692            database.database.get(
    4531                 'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4693                'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    45324694                [personalId, storeId],
    45334695                (err, ownsStore) => {
    … …  
    45584720            if (!storeId) {
    45594721                database.database.get(
    4560                     'SELECT store_id FROM works_in_store WHERE personal_id = ? LIMIT 1',
     4722                    'SELECT store_id FROM works_in_store WHERE personal_id = $1 LIMIT 1',
    45614723                    [personalId],
    45624724                    (err, store) => {
    … …  
    45834745
    45844746            database.database.get(
    4585                 'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4747                'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    45864748                [personalId, storeId],
    45874749                (err, ownsStore) => {
    … …  
    46844846
    46854847                database.database.get(
    4686                     'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4848                    'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    46874849                    [personalId, storeId],
    46884850                    (err, ownsStore) => {
    … …  
    47534915
    47544916                database.database.get(
    4755                     'SELECT store_id FROM works_in_store WHERE personal_id = ? AND store_id = ?',
     4917                    'SELECT store_id FROM works_in_store WHERE personal_id = $1 AND store_id = $2',
    47564918                    [personalId, storeId],
    47574919                    (err, ownsStore) => {
    … …  
    47654927
    47664928                        database.database.run(
    4767                             'INSERT INTO report (id, store_id, period, start_date, end_date, type, generated_by, generated_at) VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)',
    4768                             [reportId, storeId, period, startDate, endDate, type, personalId],
     4929                            'INSERT INTO report (date, store_ID, overall_profit, sales_trend, marketing_growth, owner_signature) VALUES (CURRENT_TIMESTAMP, $1, 0, $2, $3, $4)',
     4930                            [storeId, period, type, 'Not signed yet'],
    47694931                            function(err) {
    47704932                                if (err) {
  • view-database.js

    r6fea37e r6149556  
    33
    44const dbPath = path.join(__dirname, 'database', 'handcraft.db');
    5 const db = new sqlite3.Database(dbPath);
     5const db = new sqlite3.Database(dbPath, (err) => {
     6    if (err) {
     7        console.error('Error opening database:', err.message);
     8        process.exit(1);
     9    }
     10});
    611
    712console.log('\nšŸŽØ HANDCRAFT MARKETPLACE - DATABASE CONTENTS\n');
    … …  
    2530}
    2631
     32// Main execution chain
     33function executeChecks() {
     34    let currentCheck = 0;
     35
     36    const checks = [
     37        { name: 'clients', func: checkClients },
     38        { name: 'personal', func: checkPersonal },
     39        { name: 'store', func: checkStore },
     40        { name: 'product', func: checkProduct },
     41        { name: 'category', func: checkCategory },
     42        { name: 'works_in_store', func: checkWorksInStore },
     43        { name: 'permissions', func: checkPermissions },
     44        { name: 'employees', func: checkEmployees },
     45        { name: 'boss', func: checkBoss },
     46        { name: 'order', func: checkOrder },
     47        { name: 'report', func: checkReport },
     48        { name: 'refund', func: checkRefund },
     49        { name: 'image', func: checkImage },
     50        { name: 'color', func: checkColor }
     51    ];
     52
     53    function next() {
     54        currentCheck++;
     55        if (currentCheck < checks.length) {
     56            checks[currentCheck].func(next);
     57        } else {
     58            finish();
     59        }
     60    }
     61
     62    // Start with first check
     63    checks[0].func(next);
     64}
     65
    2766// Display CLIENTS table
    28 console.log('\nšŸ‘¤ CLIENTS TABLE:');
    29 console.log('================================================================================');
    30 tableExists('client', (exists) => {
    31     if (!exists) {
    32         console.log('Table does not exist');
    33         checkNext();
    34         return;
    35     }
    36 
    37     db.all('SELECT * FROM client', [], (err, rows) => {
    38         if (err) {
    39             console.log(`Error: ${err.message}`);
    40             checkNext();
    41             return;
    42         }
    43 
    44         if (!rows || rows.length === 0) {
    45             console.log('No clients found');
    46         } else {
    47             console.log(`Total: ${rows.length} clients\n`);
    48             rows.forEach((client, index) => {
    49                 console.log(`ID: ${client.client_id} | Name: ${client.first_name || ''} ${client.last_name || ''}`);
    50                 console.log(`Email: ${client.email || 'N/A'}`);
    51                 if (index < rows.length - 1) console.log('-'.repeat(80));
    52             });
    53         }
    54         checkNext();
    55     });
    56 });
     67function checkClients(next) {
     68    console.log('\nšŸ‘¤ CLIENTS TABLE:');
     69    console.log('================================================================================');
     70
     71    tableExists('client', (exists) => {
     72        if (!exists) {
     73            console.log('Table does not exist\n');
     74            if (next) next();
     75            return;
     76        }
     77
     78        db.all('SELECT * FROM client', [], (err, rows) => {
     79            if (err) {
     80                console.log(`Error: ${err.message}\n`);
     81                if (next) next();
     82                return;
     83            }
     84
     85            if (!rows || rows.length === 0) {
     86                console.log('No clients found\n');
     87            } else {
     88                console.log(`Total: ${rows.length} clients\n`);
     89                rows.forEach((client, index) => {
     90                    console.log(`ID: ${client.client_id} | Name: ${client.first_name || ''} ${client.last_name || ''}`);
     91                    console.log(`Email: ${client.email || 'N/A'}`);
     92                    if (index < rows.length - 1) console.log('-'.repeat(80));
     93                });
     94                console.log(); // Add blank line after table
     95            }
     96            if (next) next();
     97        });
     98    });
     99}
    57100
    58101// Display PERSONAL table
    59 function checkPersonal() {
     102function checkPersonal(next) {
    60103    console.log('\nšŸ‘” PERSONAL TABLE (STORE OWNERS/EMPLOYEES):');
    61104    console.log('================================================================================');
     105
    62106    tableExists('personal', (exists) => {
    63107        if (!exists) {
    64             console.log('Table does not exist');
    65             checkStore();
     108            console.log('Table does not exist\n');
     109            if (next) next();
    66110            return;
    67111        }
    … …  
    77121        `, [], (err, rows) => {
    78122            if (err) {
    79                 console.log(`Error: ${err.message}`);
    80                 checkStore();
    81                 return;
    82             }
    83 
    84             if (!rows || rows.length === 0) {
    85                 console.log('No personal records found');
     123                console.log(`Error: ${err.message}\n`);
     124                if (next) next();
     125                return;
     126            }
     127
     128            if (!rows || rows.length === 0) {
     129                console.log('No personal records found\n');
    86130            } else {
    87131                console.log(`Total: ${rows.length} personal records\n`);
    … …  
    91135                    if (index < rows.length - 1) console.log('-'.repeat(80));
    92136                });
    93             }
    94             checkStore();
     137                console.log();
     138            }
     139            if (next) next();
    95140        });
    96141    });
    … …  
    98143
    99144// Display STORE table
    100 function checkStore() {
     145function checkStore(next) {
    101146    console.log('\nšŸŖ STORE TABLE:');
    102147    console.log('================================================================================');
     148
    103149    tableExists('store', (exists) => {
    104150        if (!exists) {
    105             console.log('Table does not exist');
    106             checkProduct();
     151            console.log('Table does not exist\n');
     152            if (next) next();
    107153            return;
    108154        }
    … …  
    110156        db.all('SELECT * FROM store', [], (err, rows) => {
    111157            if (err) {
    112                 console.log(`Error: ${err.message}`);
    113                 checkProduct();
    114                 return;
    115             }
    116 
    117             if (!rows || rows.length === 0) {
    118                 console.log('No stores found');
     158                console.log(`Error: ${err.message}\n`);
     159                if (next) next();
     160                return;
     161            }
     162
     163            if (!rows || rows.length === 0) {
     164                console.log('No stores found\n');
    119165            } else {
    120166                console.log(`Total: ${rows.length} stores\n`);
    … …  
    126172                    if (index < rows.length - 1) console.log('-'.repeat(80));
    127173                });
    128             }
    129             checkProduct();
     174                console.log();
     175            }
     176            if (next) next();
    130177        });
    131178    });
    … …  
    133180
    134181// Display PRODUCT table
    135 function checkProduct() {
     182function checkProduct(next) {
    136183    console.log('\nšŸ›ļø PRODUCT TABLE:');
    137184    console.log('================================================================================');
     185
    138186    tableExists('product', (exists) => {
    139187        if (!exists) {
    140             console.log('Table does not exist');
    141             checkCategory();
     188            console.log('Table does not exist\n');
     189            if (next) next();
    142190            return;
    143191        }
    … …  
    149197        `, [], (err, rows) => {
    150198            if (err) {
    151                 console.log(`Error: ${err.message}`);
    152                 checkCategory();
    153                 return;
    154             }
    155 
    156             if (!rows || rows.length === 0) {
    157                 console.log('No products found');
     199                console.log(`Error: ${err.message}\n`);
     200                if (next) next();
     201                return;
     202            }
     203
     204            if (!rows || rows.length === 0) {
     205                console.log('No products found\n');
    158206            } else {
    159207                console.log(`Total: ${rows.length} products\n`);
    … …  
    165213                    if (index < rows.length - 1) console.log('-'.repeat(80));
    166214                });
    167             }
    168             checkCategory();
     215                console.log();
     216            }
     217            if (next) next();
    169218        });
    170219    });
    … …  
    172221
    173222// Display CATEGORY table
    174 function checkCategory() {
     223function checkCategory(next) {
    175224    console.log('\nšŸ“ CATEGORY TABLE:');
    176225    console.log('================================================================================');
     226
    177227    tableExists('category', (exists) => {
    178228        if (!exists) {
    179             console.log('Table does not exist');
    180             checkWorksInStore();
     229            console.log('Table does not exist\n');
     230            if (next) next();
    181231            return;
    182232        }
    … …  
    188238        `, [], (err, rows) => {
    189239            if (err) {
    190                 console.log(`Error: ${err.message}`);
    191                 checkWorksInStore();
    192                 return;
    193             }
    194 
    195             if (!rows || rows.length === 0) {
    196                 console.log('No categories found');
     240                console.log(`Error: ${err.message}\n`);
     241                if (next) next();
     242                return;
     243            }
     244
     245            if (!rows || rows.length === 0) {
     246                console.log('No categories found\n');
    197247            } else {
    198248                console.log(`Total: ${rows.length} categories\n`);
    … …  
    203253                    if (index < rows.length - 1) console.log('-'.repeat(80));
    204254                });
    205             }
    206             checkWorksInStore();
     255                console.log();
     256            }
     257            if (next) next();
    207258        });
    208259    });
    … …  
    210261
    211262// Display WORKS_IN_STORE table
    212 function checkWorksInStore() {
     263function checkWorksInStore(next) {
    213264    console.log('\nšŸ”— WORKS_IN_STORE TABLE:');
    214265    console.log('================================================================================');
     266
    215267    tableExists('works_in_store', (exists) => {
    216268        if (!exists) {
    217             console.log('Table does not exist');
    218             checkPermissions();
     269            console.log('Table does not exist\n');
     270            if (next) next();
    219271            return;
    220272        }
    … …  
    227279        `, [], (err, rows) => {
    228280            if (err) {
    229                 console.log(`Error: ${err.message}`);
    230                 checkPermissions();
    231                 return;
    232             }
    233 
    234             if (!rows || rows.length === 0) {
    235                 console.log('No assignments found');
     281                console.log(`Error: ${err.message}\n`);
     282                if (next) next();
     283                return;
     284            }
     285
     286            if (!rows || rows.length === 0) {
     287                console.log('No assignments found\n');
    236288            } else {
    237289                console.log(`Total: ${rows.length} assignments\n`);
    … …  
    241293                    if (index < rows.length - 1) console.log('-'.repeat(80));
    242294                });
    243             }
    244             checkPermissions();
     295                console.log();
     296            }
     297            if (next) next();
    245298        });
    246299    });
    … …  
    248301
    249302// Display PERMISSIONS table
    250 function checkPermissions() {
     303function checkPermissions(next) {
    251304    console.log('\nšŸ” PERMISSIONS TABLE:');
    252305    console.log('================================================================================');
     306
    253307    tableExists('permissions', (exists) => {
    254308        if (!exists) {
    255             console.log('Table does not exist');
    256             checkEmployees();
     309            console.log('Table does not exist\n');
     310            if (next) next();
    257311            return;
    258312        }
    … …  
    264318        `, [], (err, rows) => {
    265319            if (err) {
    266                 console.log(`Error: ${err.message}`);
    267                 checkEmployees();
    268                 return;
    269             }
    270 
    271             if (!rows || rows.length === 0) {
    272                 console.log('No permissions found');
     320                console.log(`Error: ${err.message}\n`);
     321                if (next) next();
     322                return;
     323            }
     324
     325            if (!rows || rows.length === 0) {
     326                console.log('No permissions found\n');
    273327            } else {
    274328                console.log(`Total: ${rows.length} permissions\n`);
    … …  
    278332                    if (index < rows.length - 1) console.log('-'.repeat(80));
    279333                });
    280             }
    281             checkEmployees();
     334                console.log();
     335            }
     336            if (next) next();
    282337        });
    283338    });
    … …  
    285340
    286341// Display EMPLOYEES table
    287 function checkEmployees() {
     342function checkEmployees(next) {
    288343    console.log('\nšŸ‘· EMPLOYEES TABLE:');
    289344    console.log('================================================================================');
     345
    290346    tableExists('employees', (exists) => {
    291347        if (!exists) {
    292             console.log('Table does not exist');
    293             checkBoss();
     348            console.log('Table does not exist\n');
     349            if (next) next();
    294350            return;
    295351        }
    … …  
    301357        `, [], (err, rows) => {
    302358            if (err) {
    303                 console.log(`Error: ${err.message}`);
    304                 checkBoss();
    305                 return;
    306             }
    307 
    308             if (!rows || rows.length === 0) {
    309                 console.log('No employees found');
     359                console.log(`Error: ${err.message}\n`);
     360                if (next) next();
     361                return;
     362            }
     363
     364            if (!rows || rows.length === 0) {
     365                console.log('No employees found\n');
    310366            } else {
    311367                console.log(`Total: ${rows.length} employees\n`);
    … …  
    315371                    if (index < rows.length - 1) console.log('-'.repeat(80));
    316372                });
    317             }
    318             checkBoss();
     373                console.log();
     374            }
     375            if (next) next();
    319376        });
    320377    });
    … …  
    322379
    323380// Display BOSS table
    324 function checkBoss() {
     381function checkBoss(next) {
    325382    console.log('\nšŸ‘‘ BOSS TABLE:');
    326383    console.log('================================================================================');
     384
    327385    tableExists('boss', (exists) => {
    328386        if (!exists) {
    329             console.log('Table does not exist');
    330             checkOrder();
     387            console.log('Table does not exist\n');
     388            if (next) next();
    331389            return;
    332390        }
    … …  
    338396        `, [], (err, rows) => {
    339397            if (err) {
    340                 console.log(`Error: ${err.message}`);
    341                 checkOrder();
    342                 return;
    343             }
    344 
    345             if (!rows || rows.length === 0) {
    346                 console.log('No bosses found');
     398                console.log(`Error: ${err.message}\n`);
     399                if (next) next();
     400                return;
     401            }
     402
     403            if (!rows || rows.length === 0) {
     404                console.log('No bosses found\n');
    347405            } else {
    348406                console.log(`Total: ${rows.length} bosses\n`);
    … …  
    352410                    if (index < rows.length - 1) console.log('-'.repeat(80));
    353411                });
    354             }
    355             checkOrder();
     412                console.log();
     413            }
     414            if (next) next();
    356415        });
    357416    });
    … …  
    359418
    360419// Display ORDER table
    361 function checkOrder() {
     420function checkOrder(next) {
    362421    console.log('\nšŸ“¦ ORDERS TABLE:');
    363422    console.log('================================================================================');
     423
    364424    tableExists('order', (exists) => {
    365425        if (!exists) {
    366             console.log('Table does not exist');
    367             checkReport();
     426            console.log('Table does not exist\n');
     427            if (next) next();
    368428            return;
    369429        }
    … …  
    376436        `, [], (err, rows) => {
    377437            if (err) {
    378                 console.log(`Error: ${err.message}`);
    379                 checkReport();
    380                 return;
    381             }
    382 
    383             if (!rows || rows.length === 0) {
    384                 console.log('No orders found');
     438                console.log(`Error: ${err.message}\n`);
     439                if (next) next();
     440                return;
     441            }
     442
     443            if (!rows || rows.length === 0) {
     444                console.log('No orders found\n');
    385445            } else {
    386446                console.log(`Total: ${rows.length} orders\n`);
    … …  
    392452                    if (index < rows.length - 1) console.log('-'.repeat(80));
    393453                });
    394             }
    395             checkReport();
     454                console.log();
     455            }
     456            if (next) next();
    396457        });
    397458    });
    … …  
    399460
    400461// Display REPORT table
    401 function checkReport() {
     462function checkReport(next) {
    402463    console.log('\nšŸ“Š REPORTS TABLE:');
    403464    console.log('================================================================================');
     465
    404466    tableExists('report', (exists) => {
    405467        if (!exists) {
    406             console.log('Table does not exist');
    407             checkRefund();
     468            console.log('Table does not exist\n');
     469            if (next) next();
    408470            return;
    409471        }
    … …  
    411473        db.all('SELECT * FROM report', [], (err, rows) => {
    412474            if (err) {
    413                 console.log(`Error: ${err.message}`);
    414                 checkRefund();
    415                 return;
    416             }
    417 
    418             if (!rows || rows.length === 0) {
    419                 console.log('No reports found');
     475                console.log(`Error: ${err.message}\n`);
     476                if (next) next();
     477                return;
     478            }
     479
     480            if (!rows || rows.length === 0) {
     481                console.log('No reports found\n');
    420482            } else {
    421483                console.log(`Total: ${rows.length} reports\n`);
    … …  
    425487                    if (index < rows.length - 1) console.log('-'.repeat(80));
    426488                });
    427             }
    428             checkRefund();
     489                console.log();
     490            }
     491            if (next) next();
    429492        });
    430493    });
    … …  
    432495
    433496// Display REFUND table
    434 function checkRefund() {
     497function checkRefund(next) {
    435498    console.log('\nšŸ’° REFUND TABLE:');
    436499    console.log('================================================================================');
     500
    437501    tableExists('refund', (exists) => {
    438502        if (!exists) {
    439             console.log('Table does not exist');
    440             checkImage();
     503            console.log('Table does not exist\n');
     504            if (next) next();
    441505            return;
    442506        }
    … …  
    444508        db.all('SELECT * FROM refund', [], (err, rows) => {
    445509            if (err) {
    446                 console.log(`Error: ${err.message}`);
    447                 checkImage();
    448                 return;
    449             }
    450 
    451             if (!rows || rows.length === 0) {
    452                 console.log('No refunds found');
     510                console.log(`Error: ${err.message}\n`);
     511                if (next) next();
     512                return;
     513            }
     514
     515            if (!rows || rows.length === 0) {
     516                console.log('No refunds found\n');
    453517            } else {
    454518                console.log(`Total: ${rows.length} refunds\n`);
    … …  
    459523                    if (index < rows.length - 1) console.log('-'.repeat(80));
    460524                });
    461             }
    462             checkImage();
     525                console.log();
     526            }
     527            if (next) next();
    463528        });
    464529    });
    … …  
    466531
    467532// Display IMAGE table
    468 function checkImage() {
     533function checkImage(next) {
    469534    console.log('\nšŸ–¼ļø IMAGE TABLE:');
    470535    console.log('================================================================================');
     536
    471537    tableExists('image', (exists) => {
    472538        if (!exists) {
    473             console.log('Table does not exist');
    474             checkColor();
     539            console.log('Table does not exist\n');
     540            if (next) next();
    475541            return;
    476542        }
    … …  
    478544        db.all('SELECT * FROM image', [], (err, rows) => {
    479545            if (err) {
    480                 console.log(`Error: ${err.message}`);
    481                 checkColor();
    482                 return;
    483             }
    484 
    485             if (!rows || rows.length === 0) {
    486                 console.log('No images found');
     546                console.log(`Error: ${err.message}\n`);
     547                if (next) next();
     548                return;
     549            }
     550
     551            if (!rows || rows.length === 0) {
     552                console.log('No images found\n');
    487553            } else {
    488554                console.log(`Total: ${rows.length} images\n`);
    … …  
    491557                    if (index < rows.length - 1) console.log('-'.repeat(80));
    492558                });
    493             }
    494             checkColor();
     559                console.log();
     560            }
     561            if (next) next();
    495562        });
    496563    });
    … …  
    498565
    499566// Display COLOR table
    500 function checkColor() {
     567function checkColor(next) {
    501568    console.log('\nšŸŽØ COLOR TABLE:');
    502569    console.log('================================================================================');
     570
    503571    tableExists('color', (exists) => {
    504572        if (!exists) {
    505             console.log('Table does not exist');
    506             finish();
     573            console.log('Table does not exist\n');
     574            if (next) next();
    507575            return;
    508576        }
    … …  
    510578        db.all('SELECT * FROM color', [], (err, rows) => {
    511579            if (err) {
    512                 console.log(`Error: ${err.message}`);
    513                 finish();
    514                 return;
    515             }
    516 
    517             if (!rows || rows.length === 0) {
    518                 console.log('No colors found');
     580                console.log(`Error: ${err.message}\n`);
     581                if (next) next();
     582                return;
     583            }
     584
     585            if (!rows || rows.length === 0) {
     586                console.log('No colors found\n');
    519587            } else {
    520588                console.log(`Total: ${rows.length} colors\n`);
    … …  
    523591                    if (index < rows.length - 1) console.log('-'.repeat(80));
    524592                });
    525             }
    526             finish();
    527         });
    528     });
    529 }
    530 
    531 // Start the chain of checks
    532 function checkNext() {
    533     checkPersonal();
    534 }
    535 
     593                console.log();
     594            }
     595            if (next) next();
     596        });
     597    });
     598}
     599
     600// Finish and close database
    536601function finish() {
    537     console.log('\n' + '='.repeat(80));
     602    console.log('='.repeat(80));
    538603    console.log('āœ… Database inspection complete');
    539604    console.log('='.repeat(80) + '\n');
    540605
    541     db.close();
    542 }
    543 
    544 // Start with CLIENTS
    545 checkNext();
     606    db.close((err) => {
     607        if (err) {
     608            console.error('Error closing database:', err.message);
     609        }
     610    });
     611}
     612
     613// Start the execution chain
     614executeChecks();
Note: See TracChangeset for help on using the changeset viewer.