Changes in / [6fea37e:6149556]
- Files:
-
- 578 added
- 6 edited
-
.env (modified) (1 diff)
-
.idea/.name (added)
-
.idea/editor.xml (added)
-
README.md (modified) (2 diffs)
-
database.js (modified) (18 diffs)
-
database/Advanced Database Developement/Triggers/Automatic calculation of store rating from order reviews.txt (added)
-
database/Advanced Database Developement/Triggers/Automatic deletion of product changes when the product is deleted.txt (added)
-
database/Advanced Database Developement/Triggers/Automatic setting of the last modified date for orders.txt (added)
-
database/Advanced Database Developement/Triggers/Automatic update of employee-store statistics after an employee is hired.txt (added)
-
database/Advanced Database Developement/Triggers/Automatic update of product availability after an order.txt (added)
-
database/Advanced Database Developement/Triggers/Prevention of deleting stores with existing orders or reports.txt (added)
-
database/Advanced Database Developement/Triggers/Prevention of ordering unavailable products.txt (added)
-
database/Advanced Database Developement/Triggers/Validation of employee authorization before making a product change.txt (added)
-
database/Advanced Database Developement/Views/Complete overview of customer orders.txt (added)
-
database/Advanced Database Developement/Views/Complete overview of products and stores.txt (added)
-
database/Advanced Database Developement/Views/Customer order history.txt (added)
-
database/Advanced Database Developement/Views/Customer request and response overview.txt (added)
-
database/Advanced Database Developement/Views/Employee workload and salary report.txt (added)
-
database/Advanced Database Developement/Views/Monthly sales and profit per store.txt (added)
-
database/Advanced Database Developement/Views/Store inventory overview.txt (added)
-
database/Advanced Database Developement/Views/Store performance overview.txt (added)
-
database/Advanced Reports for database/Approximate number of orders per client (added)
-
database/Advanced Reports for database/Clients ordered by number of orders.txt (added)
-
database/Advanced Reports for database/Each product's monthly sales.txt (added)
-
database/Advanced Reports for database/Each store's average pay.txt (added)
-
database/Advanced Reports for database/Each store's number of request, including how many have been solved, and how many are still in progress.txt (added)
-
database/Advanced Reports for database/Employees ordered by total hours worked and total pay.txt (added)
-
database/Advanced Reports for database/List of clients who haven't made an order yet.txt (added)
-
database/Advanced Reports for database/List of most popular products with total number of sales.txt (added)
-
database/Advanced Reports for database/List of products that are low on stock and high in demand.txt (added)
-
database/Advanced Reports for database/List of products who have not been ordered.txt (added)
-
database/Advanced Reports for database/List of reports which haven't been approved.txt (added)
-
database/Advanced Reports for database/Number of product changes each employee has made in the last month.txt (added)
-
database/Advanced Reports for database/Orders ordered by order total from highest to lowest.txt (added)
-
database/Advanced Reports for database/Products ordered by number of orders from highest to lowest.txt (added)
-
database/Advanced Reports for database/Store with highest revenue growth in the last calendar year.txt (added)
-
database/Advanced Reports for database/Stores ordered by highest approximate product review.txt (added)
-
database/Advanced Reports for database/Stores ordered by monthly profit including monthly revenue growth.txt (added)
-
database/Advanced Reports for database/Stores ordered by total revenue in the last calendar year from highest to lowest.txt (added)
-
database/Advanced Reports for database/Top 10 employees who have answered the most amount of request in the last month.txt (added)
-
node_modules/.bin/nodemon (added)
-
node_modules/.bin/nodemon.cmd (added)
-
node_modules/.bin/nodemon.ps1 (added)
-
node_modules/.bin/nodetouch (added)
-
node_modules/.bin/nodetouch.cmd (added)
-
node_modules/.bin/nodetouch.ps1 (added)
-
node_modules/.bin/semver (added)
-
node_modules/.bin/semver.cmd (added)
-
node_modules/.bin/semver.ps1 (added)
-
node_modules/.package-lock.json (added)
-
node_modules/anymatch/LICENSE (added)
-
node_modules/anymatch/README.md (added)
-
node_modules/anymatch/index.d.ts (added)
-
node_modules/anymatch/index.js (added)
-
node_modules/anymatch/package.json (added)
-
node_modules/balanced-match/LICENSE.md (added)
-
node_modules/balanced-match/README.md (added)
-
node_modules/balanced-match/dist/commonjs/index.d.ts (added)
-
node_modules/balanced-match/dist/commonjs/index.d.ts.map (added)
-
node_modules/balanced-match/dist/commonjs/index.js (added)
-
node_modules/balanced-match/dist/commonjs/index.js.map (added)
-
node_modules/balanced-match/dist/commonjs/package.json (added)
-
node_modules/balanced-match/dist/esm/index.d.ts (added)
-
node_modules/balanced-match/dist/esm/index.d.ts.map (added)
-
node_modules/balanced-match/dist/esm/index.js (added)
-
node_modules/balanced-match/dist/esm/index.js.map (added)
-
node_modules/balanced-match/dist/esm/package.json (added)
-
node_modules/balanced-match/package.json (added)
-
node_modules/bcryptjs/.npmignore (added)
-
node_modules/bcryptjs/.travis.yml (added)
-
node_modules/bcryptjs/LICENSE (added)
-
node_modules/bcryptjs/README.md (added)
-
node_modules/bcryptjs/bower.json (added)
-
node_modules/bcryptjs/dist/README.md (added)
-
node_modules/bcryptjs/dist/bcrypt.js (added)
-
node_modules/bcryptjs/dist/bcrypt.min.js (added)
-
node_modules/bcryptjs/dist/bcrypt.min.js.gz (added)
-
node_modules/bcryptjs/dist/bcrypt.min.map (added)
-
node_modules/bcryptjs/externs/bcrypt.js (added)
-
node_modules/bcryptjs/externs/minimal-env.js (added)
-
node_modules/bcryptjs/index.js (added)
-
node_modules/bcryptjs/package.json (added)
-
node_modules/bcryptjs/scripts/build.js (added)
-
node_modules/bcryptjs/src/bcrypt.js (added)
-
node_modules/bcryptjs/src/bcrypt/impl.js (added)
-
node_modules/bcryptjs/src/bcrypt/prng/README.md (added)
-
node_modules/bcryptjs/src/bcrypt/prng/accum.js (added)
-
node_modules/bcryptjs/src/bcrypt/prng/isaac.js (added)
-
node_modules/bcryptjs/src/bcrypt/util.js (added)
-
node_modules/bcryptjs/src/bcrypt/util/base64.js (added)
-
node_modules/bcryptjs/src/bower.json (added)
-
node_modules/bcryptjs/src/wrap.js (added)
-
node_modules/bcryptjs/tests/quickbrown.txt (added)
-
node_modules/bcryptjs/tests/suite.js (added)
-
node_modules/binary-extensions/binary-extensions.json (added)
-
node_modules/binary-extensions/binary-extensions.json.d.ts (added)
-
node_modules/binary-extensions/index.d.ts (added)
-
node_modules/binary-extensions/index.js (added)
-
node_modules/binary-extensions/license (added)
-
node_modules/binary-extensions/package.json (added)
-
node_modules/binary-extensions/readme.md (added)
-
node_modules/brace-expansion/LICENSE (added)
-
node_modules/brace-expansion/README.md (added)
-
node_modules/brace-expansion/dist/commonjs/index.d.ts (added)
-
node_modules/brace-expansion/dist/commonjs/index.d.ts.map (added)
-
node_modules/brace-expansion/dist/commonjs/index.js (added)
-
node_modules/brace-expansion/dist/commonjs/index.js.map (added)
-
node_modules/brace-expansion/dist/commonjs/package.json (added)
-
node_modules/brace-expansion/dist/esm/index.d.ts (added)
-
node_modules/brace-expansion/dist/esm/index.d.ts.map (added)
-
node_modules/brace-expansion/dist/esm/index.js (added)
-
node_modules/brace-expansion/dist/esm/index.js.map (added)
-
node_modules/brace-expansion/dist/esm/package.json (added)
-
node_modules/brace-expansion/package.json (added)
-
node_modules/braces/LICENSE (added)
-
node_modules/braces/README.md (added)
-
node_modules/braces/index.js (added)
-
node_modules/braces/lib/compile.js (added)
-
node_modules/braces/lib/constants.js (added)
-
node_modules/braces/lib/expand.js (added)
-
node_modules/braces/lib/parse.js (added)
-
node_modules/braces/lib/stringify.js (added)
-
node_modules/braces/lib/utils.js (added)
-
node_modules/braces/package.json (added)
-
node_modules/chokidar/LICENSE (added)
-
node_modules/chokidar/README.md (added)
-
node_modules/chokidar/index.js (added)
-
node_modules/chokidar/lib/constants.js (added)
-
node_modules/chokidar/lib/fsevents-handler.js (added)
-
node_modules/chokidar/lib/nodefs-handler.js (added)
-
node_modules/chokidar/package.json (added)
-
node_modules/chokidar/types/index.d.ts (added)
-
node_modules/debug/LICENSE (added)
-
node_modules/debug/README.md (added)
-
node_modules/debug/package.json (added)
-
node_modules/debug/src/browser.js (added)
-
node_modules/debug/src/common.js (added)
-
node_modules/debug/src/index.js (added)
-
node_modules/debug/src/node.js (added)
-
node_modules/dotenv/CHANGELOG.md (added)
-
node_modules/dotenv/LICENSE (added)
-
node_modules/dotenv/README-es.md (added)
-
node_modules/dotenv/README.md (added)
-
node_modules/dotenv/SECURITY.md (added)
-
node_modules/dotenv/config.d.ts (added)
-
node_modules/dotenv/config.js (added)
-
node_modules/dotenv/lib/cli-options.js (added)
-
node_modules/dotenv/lib/env-options.js (added)
-
node_modules/dotenv/lib/main.d.ts (added)
-
node_modules/dotenv/lib/main.js (added)
-
node_modules/dotenv/package.json (added)
-
node_modules/fill-range/LICENSE (added)
-
node_modules/fill-range/README.md (added)
-
node_modules/fill-range/index.js (added)
-
node_modules/fill-range/package.json (added)
-
node_modules/glob-parent/CHANGELOG.md (added)
-
node_modules/glob-parent/LICENSE (added)
-
node_modules/glob-parent/README.md (added)
-
node_modules/glob-parent/index.js (added)
-
node_modules/glob-parent/package.json (added)
-
node_modules/has-flag/index.js (added)
-
node_modules/has-flag/license (added)
-
node_modules/has-flag/package.json (added)
-
node_modules/has-flag/readme.md (added)
-
node_modules/ignore-by-default/LICENSE (added)
-
node_modules/ignore-by-default/README.md (added)
-
node_modules/ignore-by-default/index.js (added)
-
node_modules/ignore-by-default/package.json (added)
-
node_modules/is-binary-path/index.d.ts (added)
-
node_modules/is-binary-path/index.js (added)
-
node_modules/is-binary-path/license (added)
-
node_modules/is-binary-path/package.json (added)
-
node_modules/is-binary-path/readme.md (added)
-
node_modules/is-extglob/LICENSE (added)
-
node_modules/is-extglob/README.md (added)
-
node_modules/is-extglob/index.js (added)
-
node_modules/is-extglob/package.json (added)
-
node_modules/is-glob/LICENSE (added)
-
node_modules/is-glob/README.md (added)
-
node_modules/is-glob/index.js (added)
-
node_modules/is-glob/package.json (added)
-
node_modules/is-number/LICENSE (added)
-
node_modules/is-number/README.md (added)
-
node_modules/is-number/index.js (added)
-
node_modules/is-number/package.json (added)
-
node_modules/minimatch/LICENSE.md (added)
-
node_modules/minimatch/README.md (added)
-
node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts (added)
-
node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map (added)
-
node_modules/minimatch/dist/commonjs/assert-valid-pattern.js (added)
-
node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map (added)
-
node_modules/minimatch/dist/commonjs/ast.d.ts (added)
-
node_modules/minimatch/dist/commonjs/ast.d.ts.map (added)
-
node_modules/minimatch/dist/commonjs/ast.js (added)
-
node_modules/minimatch/dist/commonjs/ast.js.map (added)
-
node_modules/minimatch/dist/commonjs/brace-expressions.d.ts (added)
-
node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map (added)
-
node_modules/minimatch/dist/commonjs/brace-expressions.js (added)
-
node_modules/minimatch/dist/commonjs/brace-expressions.js.map (added)
-
node_modules/minimatch/dist/commonjs/escape.d.ts (added)
-
node_modules/minimatch/dist/commonjs/escape.d.ts.map (added)
-
node_modules/minimatch/dist/commonjs/escape.js (added)
-
node_modules/minimatch/dist/commonjs/escape.js.map (added)
-
node_modules/minimatch/dist/commonjs/index.d.ts (added)
-
node_modules/minimatch/dist/commonjs/index.d.ts.map (added)
-
node_modules/minimatch/dist/commonjs/index.js (added)
-
node_modules/minimatch/dist/commonjs/index.js.map (added)
-
node_modules/minimatch/dist/commonjs/package.json (added)
-
node_modules/minimatch/dist/commonjs/unescape.d.ts (added)
-
node_modules/minimatch/dist/commonjs/unescape.d.ts.map (added)
-
node_modules/minimatch/dist/commonjs/unescape.js (added)
-
node_modules/minimatch/dist/commonjs/unescape.js.map (added)
-
node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts (added)
-
node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map (added)
-
node_modules/minimatch/dist/esm/assert-valid-pattern.js (added)
-
node_modules/minimatch/dist/esm/assert-valid-pattern.js.map (added)
-
node_modules/minimatch/dist/esm/ast.d.ts (added)
-
node_modules/minimatch/dist/esm/ast.d.ts.map (added)
-
node_modules/minimatch/dist/esm/ast.js (added)
-
node_modules/minimatch/dist/esm/ast.js.map (added)
-
node_modules/minimatch/dist/esm/brace-expressions.d.ts (added)
-
node_modules/minimatch/dist/esm/brace-expressions.d.ts.map (added)
-
node_modules/minimatch/dist/esm/brace-expressions.js (added)
-
node_modules/minimatch/dist/esm/brace-expressions.js.map (added)
-
node_modules/minimatch/dist/esm/escape.d.ts (added)
-
node_modules/minimatch/dist/esm/escape.d.ts.map (added)
-
node_modules/minimatch/dist/esm/escape.js (added)
-
node_modules/minimatch/dist/esm/escape.js.map (added)
-
node_modules/minimatch/dist/esm/index.d.ts (added)
-
node_modules/minimatch/dist/esm/index.d.ts.map (added)
-
node_modules/minimatch/dist/esm/index.js (added)
-
node_modules/minimatch/dist/esm/index.js.map (added)
-
node_modules/minimatch/dist/esm/package.json (added)
-
node_modules/minimatch/dist/esm/unescape.d.ts (added)
-
node_modules/minimatch/dist/esm/unescape.d.ts.map (added)
-
node_modules/minimatch/dist/esm/unescape.js (added)
-
node_modules/minimatch/dist/esm/unescape.js.map (added)
-
node_modules/minimatch/package.json (added)
-
node_modules/ms/index.js (added)
-
node_modules/ms/license.md (added)
-
node_modules/ms/package.json (added)
-
node_modules/ms/readme.md (added)
-
node_modules/nodemailer/.gitattributes (added)
-
node_modules/nodemailer/.ncurc.js (added)
-
node_modules/nodemailer/.prettierrc.js (added)
-
node_modules/nodemailer/CHANGELOG.md (added)
-
node_modules/nodemailer/CODE_OF_CONDUCT.md (added)
-
node_modules/nodemailer/LICENSE (added)
-
node_modules/nodemailer/README.md (added)
-
node_modules/nodemailer/SECURITY.txt (added)
-
node_modules/nodemailer/lib/addressparser/index.js (added)
-
node_modules/nodemailer/lib/base64/index.js (added)
-
node_modules/nodemailer/lib/dkim/index.js (added)
-
node_modules/nodemailer/lib/dkim/message-parser.js (added)
-
node_modules/nodemailer/lib/dkim/relaxed-body.js (added)
-
node_modules/nodemailer/lib/dkim/sign.js (added)
-
node_modules/nodemailer/lib/fetch/cookies.js (added)
-
node_modules/nodemailer/lib/fetch/index.js (added)
-
node_modules/nodemailer/lib/json-transport/index.js (added)
-
node_modules/nodemailer/lib/mail-composer/index.js (added)
-
node_modules/nodemailer/lib/mailer/index.js (added)
-
node_modules/nodemailer/lib/mailer/mail-message.js (added)
-
node_modules/nodemailer/lib/mime-funcs/index.js (added)
-
node_modules/nodemailer/lib/mime-funcs/mime-types.js (added)
-
node_modules/nodemailer/lib/mime-node/index.js (added)
-
node_modules/nodemailer/lib/mime-node/last-newline.js (added)
-
node_modules/nodemailer/lib/mime-node/le-unix.js (added)
-
node_modules/nodemailer/lib/mime-node/le-windows.js (added)
-
node_modules/nodemailer/lib/nodemailer.js (added)
-
node_modules/nodemailer/lib/punycode/index.js (added)
-
node_modules/nodemailer/lib/qp/index.js (added)
-
node_modules/nodemailer/lib/sendmail-transport/index.js (added)
-
node_modules/nodemailer/lib/ses-transport/index.js (added)
-
node_modules/nodemailer/lib/shared/index.js (added)
-
node_modules/nodemailer/lib/smtp-connection/data-stream.js (added)
-
node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js (added)
-
node_modules/nodemailer/lib/smtp-connection/index.js (added)
-
node_modules/nodemailer/lib/smtp-pool/index.js (added)
-
node_modules/nodemailer/lib/smtp-pool/pool-resource.js (added)
-
node_modules/nodemailer/lib/smtp-transport/index.js (added)
-
node_modules/nodemailer/lib/stream-transport/index.js (added)
-
node_modules/nodemailer/lib/well-known/index.js (added)
-
node_modules/nodemailer/lib/well-known/services.json (added)
-
node_modules/nodemailer/lib/xoauth2/index.js (added)
-
node_modules/nodemailer/package.json (added)
-
node_modules/nodemon/.prettierrc.json (added)
-
node_modules/nodemon/LICENSE (added)
-
node_modules/nodemon/README.md (added)
-
node_modules/nodemon/doc/cli/authors.txt (added)
-
node_modules/nodemon/doc/cli/config.txt (added)
-
node_modules/nodemon/doc/cli/help.txt (added)
-
node_modules/nodemon/doc/cli/logo.txt (added)
-
node_modules/nodemon/doc/cli/options.txt (added)
-
node_modules/nodemon/doc/cli/topics.txt (added)
-
node_modules/nodemon/doc/cli/usage.txt (added)
-
node_modules/nodemon/doc/cli/whoami.txt (added)
-
node_modules/nodemon/index.d.ts (added)
-
node_modules/nodemon/jsconfig.json (added)
-
node_modules/nodemon/lib/cli/index.js (added)
-
node_modules/nodemon/lib/cli/parse.js (added)
-
node_modules/nodemon/lib/config/command.js (added)
-
node_modules/nodemon/lib/config/defaults.js (added)
-
node_modules/nodemon/lib/config/exec.js (added)
-
node_modules/nodemon/lib/config/index.js (added)
-
node_modules/nodemon/lib/config/load.js (added)
-
node_modules/nodemon/lib/help/index.js (added)
-
node_modules/nodemon/lib/index.js (added)
-
node_modules/nodemon/lib/monitor/index.js (added)
-
node_modules/nodemon/lib/monitor/match.js (added)
-
node_modules/nodemon/lib/monitor/run.js (added)
-
node_modules/nodemon/lib/monitor/signals.js (added)
-
node_modules/nodemon/lib/monitor/watch.js (added)
-
node_modules/nodemon/lib/nodemon.js (added)
-
node_modules/nodemon/lib/rules/add.js (added)
-
node_modules/nodemon/lib/rules/index.js (added)
-
node_modules/nodemon/lib/rules/parse.js (added)
-
node_modules/nodemon/lib/spawn.js (added)
-
node_modules/nodemon/lib/utils/bus.js (added)
-
node_modules/nodemon/lib/utils/clone.js (added)
-
node_modules/nodemon/lib/utils/colour.js (added)
-
node_modules/nodemon/lib/utils/index.js (added)
-
node_modules/nodemon/lib/utils/log.js (added)
-
node_modules/nodemon/lib/utils/merge.js (added)
-
node_modules/nodemon/lib/version.js (added)
-
node_modules/nodemon/package.json (added)
-
node_modules/normalize-path/LICENSE (added)
-
node_modules/normalize-path/README.md (added)
-
node_modules/normalize-path/index.js (added)
-
node_modules/normalize-path/package.json (added)
-
node_modules/pg-cloudflare/LICENSE (added)
-
node_modules/pg-cloudflare/README.md (added)
-
node_modules/pg-cloudflare/dist/empty.d.ts (added)
-
node_modules/pg-cloudflare/dist/empty.js (added)
-
node_modules/pg-cloudflare/dist/empty.js.map (added)
-
node_modules/pg-cloudflare/dist/index.d.ts (added)
-
node_modules/pg-cloudflare/dist/index.js (added)
-
node_modules/pg-cloudflare/dist/index.js.map (added)
-
node_modules/pg-cloudflare/esm/index.mjs (added)
-
node_modules/pg-cloudflare/package.json (added)
-
node_modules/pg-cloudflare/src/empty.ts (added)
-
node_modules/pg-cloudflare/src/index.ts (added)
-
node_modules/pg-cloudflare/src/types.d.ts (added)
-
node_modules/pg-connection-string/LICENSE (added)
-
node_modules/pg-connection-string/README.md (added)
-
node_modules/pg-connection-string/esm/index.mjs (added)
-
node_modules/pg-connection-string/index.d.ts (added)
-
node_modules/pg-connection-string/index.js (added)
-
node_modules/pg-connection-string/package.json (added)
-
node_modules/pg-int8/LICENSE (added)
-
node_modules/pg-int8/README.md (added)
-
node_modules/pg-int8/index.js (added)
-
node_modules/pg-int8/package.json (added)
-
node_modules/pg-pool/LICENSE (added)
-
node_modules/pg-pool/README.md (added)
-
node_modules/pg-pool/esm/index.mjs (added)
-
node_modules/pg-pool/index.js (added)
-
node_modules/pg-pool/package.json (added)
-
node_modules/pg-protocol/LICENSE (added)
-
node_modules/pg-protocol/README.md (added)
-
node_modules/pg-protocol/dist/b.d.ts (added)
-
node_modules/pg-protocol/dist/b.js (added)
-
node_modules/pg-protocol/dist/b.js.map (added)
-
node_modules/pg-protocol/dist/buffer-reader.d.ts (added)
-
node_modules/pg-protocol/dist/buffer-reader.js (added)
-
node_modules/pg-protocol/dist/buffer-reader.js.map (added)
-
node_modules/pg-protocol/dist/buffer-writer.d.ts (added)
-
node_modules/pg-protocol/dist/buffer-writer.js (added)
-
node_modules/pg-protocol/dist/buffer-writer.js.map (added)
-
node_modules/pg-protocol/dist/inbound-parser.test.d.ts (added)
-
node_modules/pg-protocol/dist/inbound-parser.test.js (added)
-
node_modules/pg-protocol/dist/inbound-parser.test.js.map (added)
-
node_modules/pg-protocol/dist/index.d.ts (added)
-
node_modules/pg-protocol/dist/index.js (added)
-
node_modules/pg-protocol/dist/index.js.map (added)
-
node_modules/pg-protocol/dist/messages.d.ts (added)
-
node_modules/pg-protocol/dist/messages.js (added)
-
node_modules/pg-protocol/dist/messages.js.map (added)
-
node_modules/pg-protocol/dist/outbound-serializer.test.d.ts (added)
-
node_modules/pg-protocol/dist/outbound-serializer.test.js (added)
-
node_modules/pg-protocol/dist/outbound-serializer.test.js.map (added)
-
node_modules/pg-protocol/dist/parser.d.ts (added)
-
node_modules/pg-protocol/dist/parser.js (added)
-
node_modules/pg-protocol/dist/parser.js.map (added)
-
node_modules/pg-protocol/dist/serializer.d.ts (added)
-
node_modules/pg-protocol/dist/serializer.js (added)
-
node_modules/pg-protocol/dist/serializer.js.map (added)
-
node_modules/pg-protocol/esm/index.js (added)
-
node_modules/pg-protocol/package.json (added)
-
node_modules/pg-protocol/src/b.ts (added)
-
node_modules/pg-protocol/src/buffer-reader.ts (added)
-
node_modules/pg-protocol/src/buffer-writer.ts (added)
-
node_modules/pg-protocol/src/inbound-parser.test.ts (added)
-
node_modules/pg-protocol/src/index.ts (added)
-
node_modules/pg-protocol/src/messages.ts (added)
-
node_modules/pg-protocol/src/outbound-serializer.test.ts (added)
-
node_modules/pg-protocol/src/parser.ts (added)
-
node_modules/pg-protocol/src/serializer.ts (added)
-
node_modules/pg-protocol/src/testing/buffer-list.ts (added)
-
node_modules/pg-protocol/src/testing/test-buffers.ts (added)
-
node_modules/pg-types/.travis.yml (added)
-
node_modules/pg-types/Makefile (added)
-
node_modules/pg-types/README.md (added)
-
node_modules/pg-types/index.d.ts (added)
-
node_modules/pg-types/index.js (added)
-
node_modules/pg-types/index.test-d.ts (added)
-
node_modules/pg-types/lib/arrayParser.js (added)
-
node_modules/pg-types/lib/binaryParsers.js (added)
-
node_modules/pg-types/lib/builtins.js (added)
-
node_modules/pg-types/lib/textParsers.js (added)
-
node_modules/pg-types/package.json (added)
-
node_modules/pg-types/test/index.js (added)
-
node_modules/pg-types/test/types.js (added)
-
node_modules/pg/LICENSE (added)
-
node_modules/pg/README.md (added)
-
node_modules/pg/esm/index.mjs (added)
-
node_modules/pg/lib/client.js (added)
-
node_modules/pg/lib/connection-parameters.js (added)
-
node_modules/pg/lib/connection.js (added)
-
node_modules/pg/lib/crypto/cert-signatures.js (added)
-
node_modules/pg/lib/crypto/sasl.js (added)
-
node_modules/pg/lib/crypto/utils.js (added)
-
node_modules/pg/lib/defaults.js (added)
-
node_modules/pg/lib/index.js (added)
-
node_modules/pg/lib/native/client.js (added)
-
node_modules/pg/lib/native/index.js (added)
-
node_modules/pg/lib/native/query.js (added)
-
node_modules/pg/lib/query.js (added)
-
node_modules/pg/lib/result.js (added)
-
node_modules/pg/lib/stream.js (added)
-
node_modules/pg/lib/type-overrides.js (added)
-
node_modules/pg/lib/utils.js (added)
-
node_modules/pg/package.json (added)
-
node_modules/pgpass/README.md (added)
-
node_modules/pgpass/lib/helper.js (added)
-
node_modules/pgpass/lib/index.js (added)
-
node_modules/pgpass/package.json (added)
-
node_modules/picomatch/LICENSE (added)
-
node_modules/picomatch/README.md (added)
-
node_modules/picomatch/index.js (added)
-
node_modules/picomatch/lib/constants.js (added)
-
node_modules/picomatch/lib/parse.js (added)
-
node_modules/picomatch/lib/picomatch.js (added)
-
node_modules/picomatch/lib/scan.js (added)
-
node_modules/picomatch/lib/utils.js (added)
-
node_modules/picomatch/package.json (added)
-
node_modules/postgres-array/index.d.ts (added)
-
node_modules/postgres-array/index.js (added)
-
node_modules/postgres-array/license (added)
-
node_modules/postgres-array/package.json (added)
-
node_modules/postgres-array/readme.md (added)
-
node_modules/postgres-bytea/index.js (added)
-
node_modules/postgres-bytea/license (added)
-
node_modules/postgres-bytea/package.json (added)
-
node_modules/postgres-bytea/readme.md (added)
-
node_modules/postgres-date/index.js (added)
-
node_modules/postgres-date/license (added)
-
node_modules/postgres-date/package.json (added)
-
node_modules/postgres-date/readme.md (added)
-
node_modules/postgres-interval/index.d.ts (added)
-
node_modules/postgres-interval/index.js (added)
-
node_modules/postgres-interval/license (added)
-
node_modules/postgres-interval/package.json (added)
-
node_modules/postgres-interval/readme.md (added)
-
node_modules/pstree.remy/.travis.yml (added)
-
node_modules/pstree.remy/LICENSE (added)
-
node_modules/pstree.remy/README.md (added)
-
node_modules/pstree.remy/lib/index.js (added)
-
node_modules/pstree.remy/lib/tree.js (added)
-
node_modules/pstree.remy/lib/utils.js (added)
-
node_modules/pstree.remy/package.json (added)
-
node_modules/pstree.remy/tests/fixtures/index.js (added)
-
node_modules/pstree.remy/tests/fixtures/out1 (added)
-
node_modules/pstree.remy/tests/fixtures/out2 (added)
-
node_modules/pstree.remy/tests/index.test.js (added)
-
node_modules/readdirp/LICENSE (added)
-
node_modules/readdirp/README.md (added)
-
node_modules/readdirp/index.d.ts (added)
-
node_modules/readdirp/index.js (added)
-
node_modules/readdirp/package.json (added)
-
node_modules/semver/LICENSE (added)
-
node_modules/semver/README.md (added)
-
node_modules/semver/classes/comparator.js (added)
-
node_modules/semver/classes/index.js (added)
-
node_modules/semver/classes/range.js (added)
-
node_modules/semver/classes/semver.js (added)
-
node_modules/semver/functions/clean.js (added)
-
node_modules/semver/functions/cmp.js (added)
-
node_modules/semver/functions/coerce.js (added)
-
node_modules/semver/functions/compare-build.js (added)
-
node_modules/semver/functions/compare-loose.js (added)
-
node_modules/semver/functions/compare.js (added)
-
node_modules/semver/functions/diff.js (added)
-
node_modules/semver/functions/eq.js (added)
-
node_modules/semver/functions/gt.js (added)
-
node_modules/semver/functions/gte.js (added)
-
node_modules/semver/functions/inc.js (added)
-
node_modules/semver/functions/lt.js (added)
-
node_modules/semver/functions/lte.js (added)
-
node_modules/semver/functions/major.js (added)
-
node_modules/semver/functions/minor.js (added)
-
node_modules/semver/functions/neq.js (added)
-
node_modules/semver/functions/parse.js (added)
-
node_modules/semver/functions/patch.js (added)
-
node_modules/semver/functions/prerelease.js (added)
-
node_modules/semver/functions/rcompare.js (added)
-
node_modules/semver/functions/rsort.js (added)
-
node_modules/semver/functions/satisfies.js (added)
-
node_modules/semver/functions/sort.js (added)
-
node_modules/semver/functions/valid.js (added)
-
node_modules/semver/index.js (added)
-
node_modules/semver/internal/constants.js (added)
-
node_modules/semver/internal/debug.js (added)
-
node_modules/semver/internal/identifiers.js (added)
-
node_modules/semver/internal/lrucache.js (added)
-
node_modules/semver/internal/parse-options.js (added)
-
node_modules/semver/internal/re.js (added)
-
node_modules/semver/package.json (added)
-
node_modules/semver/preload.js (added)
-
node_modules/semver/range.bnf (added)
-
node_modules/semver/ranges/gtr.js (added)
-
node_modules/semver/ranges/intersects.js (added)
-
node_modules/semver/ranges/ltr.js (added)
-
node_modules/semver/ranges/max-satisfying.js (added)
-
node_modules/semver/ranges/min-satisfying.js (added)
-
node_modules/semver/ranges/min-version.js (added)
-
node_modules/semver/ranges/outside.js (added)
-
node_modules/semver/ranges/simplify.js (added)
-
node_modules/semver/ranges/subset.js (added)
-
node_modules/semver/ranges/to-comparators.js (added)
-
node_modules/semver/ranges/valid.js (added)
-
node_modules/simple-update-notifier/LICENSE (added)
-
node_modules/simple-update-notifier/README.md (added)
-
node_modules/simple-update-notifier/build/index.d.ts (added)
-
node_modules/simple-update-notifier/build/index.js (added)
-
node_modules/simple-update-notifier/package.json (added)
-
node_modules/simple-update-notifier/src/borderedText.ts (added)
-
node_modules/simple-update-notifier/src/cache.spec.ts (added)
-
node_modules/simple-update-notifier/src/cache.ts (added)
-
node_modules/simple-update-notifier/src/getDistVersion.spec.ts (added)
-
node_modules/simple-update-notifier/src/getDistVersion.ts (added)
-
node_modules/simple-update-notifier/src/hasNewVersion.spec.ts (added)
-
node_modules/simple-update-notifier/src/hasNewVersion.ts (added)
-
node_modules/simple-update-notifier/src/index.spec.ts (added)
-
node_modules/simple-update-notifier/src/index.ts (added)
-
node_modules/simple-update-notifier/src/isNpmOrYarn.ts (added)
-
node_modules/simple-update-notifier/src/types.ts (added)
-
node_modules/split2/LICENSE (added)
-
node_modules/split2/README.md (added)
-
node_modules/split2/bench.js (added)
-
node_modules/split2/index.js (added)
-
node_modules/split2/package.json (added)
-
node_modules/split2/test.js (added)
-
node_modules/supports-color/browser.js (added)
-
node_modules/supports-color/index.js (added)
-
node_modules/supports-color/license (added)
-
node_modules/supports-color/package.json (added)
-
node_modules/supports-color/readme.md (added)
-
node_modules/to-regex-range/LICENSE (added)
-
node_modules/to-regex-range/README.md (added)
-
node_modules/to-regex-range/index.js (added)
-
node_modules/to-regex-range/package.json (added)
-
node_modules/touch/LICENSE (added)
-
node_modules/touch/README.md (added)
-
node_modules/touch/index.js (added)
-
node_modules/touch/package.json (added)
-
node_modules/undefsafe/.github/workflows/release.yml (added)
-
node_modules/undefsafe/.jscsrc (added)
-
node_modules/undefsafe/.jshintrc (added)
-
node_modules/undefsafe/.travis.yml (added)
-
node_modules/undefsafe/LICENSE (added)
-
node_modules/undefsafe/README.md (added)
-
node_modules/undefsafe/example.js (added)
-
node_modules/undefsafe/lib/undefsafe.js (added)
-
node_modules/undefsafe/package.json (added)
-
node_modules/xtend/.jshintrc (added)
-
node_modules/xtend/LICENSE (added)
-
node_modules/xtend/README.md (added)
-
node_modules/xtend/immutable.js (added)
-
node_modules/xtend/mutable.js (added)
-
node_modules/xtend/package.json (added)
-
node_modules/xtend/test.js (added)
-
package.json (modified) (1 diff)
-
server.js (modified) (83 diffs)
-
view-database.js (modified) (40 diffs)
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 7 PGHOST=localhost 8 PGPORT=9999 9 PGDATABASE=db_202526z_va_prj_handcraft_store 10 PGUSER=db_202526z_va_prj_handcraft_store_owner 11 PGPASSWORD=77769964489a 12 1 13 SMTP_HOST=smtp.ethereal.email 2 SMTP_PORT= 58714 SMTP_PORT=3000 3 15 SMTP_USER=frederik52@ethereal.email 4 16 SMTP_PASS=sgqN9t4qn7RxCdavyv 5 17 JWT_SECRET=handcraft_marketplace_secret_key_2024 6 18 PORT=3000 7 DB_PATH=./handcraft.db -
README.md
r6fea37e r6149556 3 3 * Open the project folder in Command line 4 4 * Run npm install command to install all dependencies 5 * Run npm start to lunch the program5 * Run npm start to lunch the cmdprogram 6 6 * After running the command, app is available at locatlhost:3000 7 7 * See the database with command node view-database.js … … 11 11 12 12 Klimentina Efremova 13 14 ======= -
database.js
r6fea37e r6149556 1 const sqlite3 = require('sqlite3').verbose(); 2 const path = require('path'); 1 const { Pool } = require('pg'); 3 2 const 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 } 3 require('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 24 const 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 21 33 }); 22 34 23 // Enable foreign keys 24 database.run('PRAGMA foreign_keys = ON'); 25 26 // Helper function to ensure general category exists with ID 1 35 pool.on('connect', () => { 36 console.log('ā Connected to PostgreSQL database'); 37 }); 38 39 pool.on('error', (err) => { 40 console.error('ā Unexpected PostgreSQL pool error:', err); 41 }); 42 43 /* 44 * ============================================================ 45 * HELPER 46 * ============================================================ 47 */ 48 49 function 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 27 66 function 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 131 function 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 42 158 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; 47 161 } 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; 74 166 } 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`, 100 174 ['General', 'General products category'], 101 function(err) { 175 (err, result) => { 176 102 177 if (err) { 103 178 callback(err, null); 104 179 } 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 107 186 callback(null, newId); 108 187 } … … 110 189 ); 111 190 } 112 }); 113 } 114 }); 115 } 116 117 // User functions 191 ); 192 } 193 ); 194 } 195 196 197 /* 198 * ============================================================ 199 * USER FUNCTIONS 200 * ============================================================ 201 */ 202 118 203 function 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 123 220 124 221 function 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 } 143 252 } 144 } 145 ); 146 }); 147 } 253 ); 254 } 255 ); 256 } 257 148 258 149 259 function createUser(id, username, email, password, userType, callback) { 260 150 261 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)`, 153 268 [id, username, email, hashedPassword, userType], 154 function(err) { 269 (err) => { 270 155 271 if (err) { 156 272 callback(err, null); … … 162 278 } 163 279 164 // Client functions 280 281 /* 282 * ============================================================ 283 * CLIENT FUNCTIONS 284 * ============================================================ 285 */ 286 165 287 function 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 170 304 171 305 function 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 176 322 177 323 function 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 183 342 if (err) { 184 343 callback(err, null); 185 344 } 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 355 function verifyClientPassword( 356 password, 357 hashedPassword, 358 callback 359 ) { 360 193 361 try { 194 const isValid = bcrypt.compareSync(password, hashedPassword); 362 363 const isValid = 364 bcrypt.compareSync(password, hashedPassword); 365 195 366 callback(null, isValid); 367 196 368 } catch (err) { 369 197 370 callback(err, false); 198 371 } 199 372 } 200 373 201 // Personal functions 374 375 /* 376 * ============================================================ 377 * PERSONAL / EMPLOYEE FUNCTIONS 378 * ============================================================ 379 */ 380 202 381 function 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 207 398 208 399 function 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 215 417 function verifyPassword(password, hashedPassword) { 216 418 return bcrypt.compareSync(password, hashedPassword); 217 419 } 218 420 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 422 function 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`, 224 436 [hashedPassword, userId], 225 function(err) { 437 (err) => { 438 226 439 callback(err); 227 440 } … … 229 442 } 230 443 231 // Product functions 444 445 /* 446 * ============================================================ 447 * PRODUCT FUNCTIONS 448 * ============================================================ 449 */ 450 232 451 function 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 240 466 const params = []; 467 let paramIndex = 1; 241 468 242 469 if (categoryId && categoryId !== 'all') { 243 query += ' AND p.category_id = ?'; 470 471 queryText += 472 ` AND p.category_id = $${paramIndex}`; 473 244 474 params.push(categoryId); 475 paramIndex++; 245 476 } 246 477 247 478 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; 250 490 } 251 491 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) { 271 500 callback(err, null); 272 501 } 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 509 function 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 } 295 561 } 562 ); 563 } 564 ); 565 } 566 ); 567 } 568 569 570 function 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; 296 603 } 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 } 337 623 } 338 } 339 ); 340 } 341 } 342 ); 343 } 624 ); 625 } 626 ); 627 } 628 ); 629 } 630 344 631 345 632 function addProduct(personalId, productData, callback) { 633 346 634 getGeneralCategoryId((err, generalCategoryId) => { 635 347 636 if (err) { 348 637 callback(err, null); … … 350 639 } 351 640 352 const categoryId = productData.category_id || generalCategoryId;353 354 // FIXED: Added validation for required fields 641 const categoryId = 642 productData.category_id || generalCategoryId; 643 355 644 if (!productData.code) { 356 callback(new Error('Product code is required'), null); 645 callback( 646 new Error('Product code is required'), 647 null 648 ); 357 649 return; 358 650 } 359 651 360 652 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 ); 362 657 return; 363 658 } 364 659 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`, 370 694 [ 371 'PROD_' + Date.now().toString().slice(-8), // Generate a unique ID695 productId, 372 696 productData.code, 373 697 productData.description || 'No description', … … 380 704 productData.store_id 381 705 ], 382 function(err) { 706 (err, result) => { 707 383 708 if (err) { 384 709 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 ); 397 801 } 398 802 ); 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 } 408 833 } 834 ); 835 }); 836 } 837 838 callback(null, returnedId); 839 } 840 ); 841 }); 842 } 843 844 845 function 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; 409 987 } 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 420 1057 if (err) { 421 console.error('Error inserting image:', err); 1058 console.error( 1059 'Error inserting color:', 1060 err 1061 ); 422 1062 } 423 1063 } … … 425 1065 }); 426 1066 } 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 1076 function 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 440 1143 ); 441 1144 }); 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 1169 function 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 1187 function 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 1210 function 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 501 1229 if (err) { 502 1230 callback(err, null); 503 1231 } 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 645 1233 callback(null, { 646 id: this.lastID,1234 id: result.rows[0].category_id, 647 1235 name: categoryData.name, 648 1236 parent_id: categoryData.parent_id, … … 654 1242 } 655 1243 656 // Store functions 1244 1245 /* 1246 * ============================================================ 1247 * STORE FUNCTIONS 1248 * ============================================================ 1249 */ 1250 657 1251 function 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 662 1268 663 1269 function 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`, 670 1280 [storeId], 671 (err, rows) => { 1281 (err, result) => { 1282 1283 callback( 1284 err, 1285 result ? result.rows : [] 1286 ); 1287 } 1288 ); 1289 } 1290 1291 1292 function 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 672 1307 if (err) { 673 1308 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 1354 function 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`, 688 1370 [storeId], 689 (err, rows) => { 1371 (err, result) => { 1372 1373 callback( 1374 err, 1375 result ? result.rows : [] 1376 ); 1377 } 1378 ); 1379 } 1380 1381 1382 function 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 1401 function 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 690 1412 if (err) { 691 1413 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; 714 1458 } 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`, 787 1477 [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 ); 791 1495 } 792 1496 ); … … 799 1503 } 800 1504 801 // Order functions 1505 1506 /* 1507 * ============================================================ 1508 * ORDER FUNCTIONS 1509 * ============================================================ 1510 */ 1511 802 1512 function 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 => { 805 1617 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 867 1621 868 1622 function 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`, 875 1633 [clientId], 876 (err, rows) => { 1634 (err, result) => { 1635 877 1636 if (err) { 878 1637 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 = []; 906 1668 } 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 913 1682 914 1683 function 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`, 921 1697 [], 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 929 1715 function 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`, 933 1734 [ 934 'REV' + Date.now().toString().slice(-8),1735 reviewId, 935 1736 reviewData.client_id, 936 1737 reviewData.product_code, … … 938 1739 reviewData.comment || '' 939 1740 ], 940 function(err) { 1741 (err, result) => { 1742 941 1743 if (err) { 942 1744 callback(err, null); 943 1745 } 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 951 1762 function 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)`, 955 1775 [ 956 1776 requestData.request_num, … … 960 1780 requestData.store_id 961 1781 ], 962 function(err) { 1782 (err) => { 1783 963 1784 if (err) { 964 1785 callback(err, null); 965 1786 } 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 973 1803 function 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())`, 977 1816 [ 978 1817 refundData.refund_id, … … 981 1820 refundData.reason 982 1821 ], 983 function(err) { 1822 (err) => { 1823 984 1824 if (err) { 985 1825 callback(err, null); 986 1826 } 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 1843 function getEmployeeTasks( 1844 personalId, 1845 storeId, 1846 callback 1847 ) { 1848 995 1849 const tasks = { 996 1850 pending_orders: [], … … 999 1853 }; 1000 1854 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 1010 1875 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 1023 1900 if (!err) { 1024 tasks.pending_requests = rows || []; 1901 tasks.pending_requests = 1902 result.rows || []; 1025 1903 } 1026 1904 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 1037 1930 if (!err) { 1038 tasks.pending_refunds = rows || []; 1931 tasks.pending_refunds = 1932 result.rows || []; 1039 1933 } 1040 callback(null, tasks); 1934 1935 callback( 1936 null, 1937 tasks 1938 ); 1041 1939 } 1042 1940 ); … … 1047 1945 } 1048 1946 1049 // Client stats functions 1947 1948 /* 1949 * ============================================================ 1950 * CLIENT STATISTICS 1951 * ============================================================ 1952 */ 1953 1050 1954 function getClientStats(clientId, callback) { 1955 1051 1956 const stats = {}; 1052 1957 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`, 1056 1962 [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`, 1066 1989 [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 ); 1084 2057 } 1085 2058 ); … … 1092 2065 } 1093 2066 1094 // User functions for admin 2067 2068 /* 2069 * ============================================================ 2070 * ADMIN USER FUNCTIONS 2071 * ============================================================ 2072 */ 2073 1095 2074 function 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 1102 2085 FROM client 1103 2086 ORDER BY client_id`, 1104 2087 [], 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 1126 2121 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 1130 2128 ORDER BY p.id`, 1131 2129 [], 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; 1141 2135 } 1142 2136 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 1150 2171 FROM users 1151 2172 ORDER BY id`, 1152 2173 [], 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'; 1173 2212 } 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; 1174 2232 }); 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 ); 1184 2238 } 1185 2239 ); … … 1190 2244 } 1191 2245 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 2253 function 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 ], 1198 2282 (err) => { 2283 1199 2284 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 */ 1205 2300 1206 2301 module.exports = { 1207 database, 2302 2303 pool, 2304 2305 query, 2306 1208 2307 ensureGeneralCategory, 1209 2308 getGeneralCategoryId, 2309 1210 2310 getUserByUsername, 1211 2311 getUserById, 1212 2312 createUser, 2313 1213 2314 getClientByEmail, 1214 2315 getClientById, 1215 2316 createClient, 1216 2317 verifyClientPassword, 2318 1217 2319 getPersonalByEmail, 1218 2320 getPersonalById, 1219 2321 verifyPassword, 1220 2322 updatePasswordAndClearForce, 2323 1221 2324 getProducts, 1222 2325 getProductById, … … 1225 2328 updateProduct, 1226 2329 deleteProduct, 2330 1227 2331 getCategories, 1228 2332 getCategoriesWithParents, 1229 2333 createCategory, 2334 1230 2335 getStores, 1231 2336 getStoreProducts, … … 1234 2339 getStoreReports, 1235 2340 getStoreStats, 2341 1236 2342 createOrderNew, 1237 2343 getOrdersByClient, 1238 2344 getAllOrders, 2345 1239 2346 createReviewNew, 2347 1240 2348 createRequest, 2349 1241 2350 createRefund, 2351 1242 2352 getEmployeeTasks, 2353 1243 2354 getClientStats, 2355 1244 2356 getAllUsers, 2357 1245 2358 logAudit 1246 2359 }; -
package.json
r6fea37e r6149556 13 13 "dotenv": "^16.3.1", 14 14 "nodemailer": "^6.9.7", 15 " sqlite3": "^5.1.6"15 "pg": "^8.23.0" 16 16 }, 17 17 "devDependencies": { -
server.js
r6fea37e r6149556 1 1 const http = require('http'); 2 2 const url = require('url'); 3 const database = require('./database.js');3 const { Pool } = require('pg'); 4 4 const fs = require('fs'); 5 5 const path = require('path'); … … 25 25 const emailConfig = { 26 26 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 })(), 28 32 secure: false, 29 33 auth: { … … 169 173 let code = ''; 170 174 for(let i = 0; i < 6; i++) { 171 code += crypto.randomInt(0, 9);175 code += crypto.randomInt(0, 10); 172 176 } 173 177 return code; … … 318 322 319 323 database.database.get( 320 'SELECT boss_id FROM boss WHERE boss_id = ?',324 'SELECT boss_id FROM boss WHERE boss_id = $1', 321 325 [personalId], 322 326 (err, boss) => { … … 339 343 } 340 344 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 347 const 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 358 let transactionClient = null; 359 360 function 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)); 423 365 } 424 366 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(); 367 const 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); 435 397 return; 436 398 } 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); 544 407 }); 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; 548 415 } 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 }); 590 428 return; 591 429 } 592 430 593 database.database.run(dropQueries[index], [], (err) =>{594 if ( err) {595 c onsole.error(`Error dropping table: ${err.message}`);596 // Continue anyway431 if (normalized === 'ROLLBACK') { 432 if (!transactionClient) { 433 callback?.(null); 434 return; 597 435 } 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 } 600 458 }); 601 459 } 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, 626 551 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 ( 642 740 id VARCHAR(50) PRIMARY KEY, 643 741 username VARCHAR(100) UNIQUE NOT NULL, … … 645 743 password VARCHAR(255) NOT NULL, 646 744 user_type VARCHAR(50) NOT NULL, 647 force_password_change INTEGER DEFAULT 0,745 force_password_change BOOLEAN DEFAULT FALSE, 648 746 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, 781 763 user_id VARCHAR(50), 782 764 action VARCHAR(100) NOT NULL, … … 786 768 ip_address VARCHAR(45), 787 769 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'] 833 792 ]; 834 793 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 ); 857 799 } 858 800 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'] 889 1006 ]; 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 } 907 1012 } 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); } 1069 1083 ); 1070 1084 } 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 () => { 1082 1243 try { 1083 await initializeDatabase();1244 await database.initializeDatabase(); 1084 1245 console.log('ā Database initialization completed'); 1085 1246 } catch (err) { 1086 1247 console.error('ā Database initialization failed:', err); 1248 process.exitCode = 1; 1087 1249 } 1088 1250 })(); … … 1158 1320 1159 1321 database.database.get( 1160 'SELECT boss_id FROM boss WHERE boss_id = ?',1322 'SELECT boss_id FROM boss WHERE boss_id = $1', 1161 1323 [personalId], 1162 1324 (err, boss) => { … … 1380 1542 1381 1543 database.database.get( 1382 'SELECT store_id FROM store WHERE store_email = ?',1544 'SELECT store_id FROM store WHERE store_email = $1', 1383 1545 [formData.storeEmail], 1384 1546 (err, existingStore) => { … … 1760 1922 // Insert into store table (store_id is VARCHAR) 1761 1923 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)', 1763 1925 [ 1764 1926 tempStoreData.storeId, … … 1780 1942 // Insert into personal table (id is VARCHAR) 1781 1943 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)', 1783 1945 [ 1784 1946 tempStoreData.personalId, … … 1809 1971 // Insert into boss table (boss_id is VARCHAR, references personal.id) 1810 1972 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], 1813 1975 (err) => { 1814 1976 if (err) { … … 1822 1984 // Insert into works_in_store table (personal_id is VARCHAR, store_id is VARCHAR) 1823 1985 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)', 1825 1987 [tempStoreData.personalId, tempStoreData.storeId], 1826 1988 (err) => { … … 1835 1997 // Insert into permissions table (personal_id is VARCHAR) 1836 1998 database.database.run( 1837 'INSERT INTO permissions (personal_id, type, authorisation) VALUES ( ?, ?, ?)',1999 'INSERT INTO permissions (personal_id, type, authorisation) VALUES ($1, $2, $3)', 1838 2000 [tempStoreData.personalId, 'BOSS', 'full_access'], 1839 2001 (err) => { … … 1844 2006 // Also create entry in users table for login with force_password_change = 1 1845 2007 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)', 1847 2009 [ 1848 2010 tempStoreData.personalId, … … 1937 2099 if (tempUserData.address && tempUserData.city && tempUserData.postcode && tempUserData.country) { 1938 2100 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)', 1940 2102 [ 1941 2103 clientId, … … 2158 2320 // Check if this is a boss (store owner) 2159 2321 database.database.get( 2160 'SELECT boss_id FROM boss WHERE boss_id = ?',2322 'SELECT boss_id FROM boss WHERE boss_id = $1', 2161 2323 [personal.id], 2162 2324 (err, boss) => { … … 2169 2331 // Check if first time login from users table 2170 2332 database.database.get( 2171 'SELECT force_password_change FROM users WHERE email = ?',2333 'SELECT force_password_change FROM users WHERE email = $1', 2172 2334 [email], 2173 2335 (err, user) => { … … 2219 2381 // Check if this is an employee 2220 2382 database.database.get( 2221 'SELECT employee_id FROM employees WHERE employee_id = ?',2383 'SELECT employee_id FROM employees WHERE employee_id = $1', 2222 2384 [personal.id], 2223 2385 (err, employee) => { … … 2229 2391 // This is an employee 2230 2392 database.database.get( 2231 'SELECT force_password_change FROM users WHERE email = ?',2393 'SELECT force_password_change FROM users WHERE email = $1', 2232 2394 [email], 2233 2395 (err, user) => { … … 2280 2442 // Treat as regular user 2281 2443 database.database.get( 2282 'SELECT * FROM users WHERE email = ?',2444 'SELECT * FROM users WHERE email = $1', 2283 2445 [email], 2284 2446 (err, user) => { … … 2422 2584 2423 2585 database.database.get( 2424 'SELECT * FROM users WHERE email = ?',2586 'SELECT * FROM users WHERE email = $1', 2425 2587 [email], 2426 2588 (err, user) => { … … 2673 2835 // Personal user (store owner/employee) 2674 2836 database.database.get( 2675 'SELECT boss_id FROM boss WHERE boss_id = ?',2837 'SELECT boss_id FROM boss WHERE boss_id = $1', 2676 2838 [userId], 2677 2839 (err, boss) => { … … 2774 2936 2775 2937 database.database.get( 2776 'SELECT boss_id FROM boss WHERE boss_id = ?',2938 'SELECT boss_id FROM boss WHERE boss_id = $1', 2777 2939 [personalId], 2778 2940 (err, boss) => { … … 2784 2946 database.database.all( 2785 2947 `SELECT s.* FROM store s 2786 JOIN works_in_store w ON s.store_id = w.store_id2787 WHERE w.personal_id = ?`,2948 JOIN works_in_store w ON s.store_id = w.store_id 2949 WHERE w.personal_id = $1`, 2788 2950 [personalId], 2789 2951 (err, stores) => { … … 2809 2971 } else { 2810 2972 database.database.get( 2811 'SELECT employee_id FROM employees WHERE employee_id = ?',2973 'SELECT employee_id FROM employees WHERE employee_id = $1', 2812 2974 [personalId], 2813 2975 (err, employee) => { … … 2819 2981 database.database.all( 2820 2982 `SELECT s.* FROM store s 2821 JOIN works_in_store w ON s.store_id = w.store_id2822 WHERE w.personal_id = ?`,2983 JOIN works_in_store w ON s.store_id = w.store_id 2984 WHERE w.personal_id = $1`, 2823 2985 [personalId], 2824 2986 (err, stores) => { … … 2944 3106 id: category.id, 2945 3107 name: category.name, 2946 parent_id: category.parent_ id,3108 parent_id: category.parent_category_id, 2947 3109 description: category.description 2948 3110 } … … 3010 3172 3011 3173 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)', 3013 3175 [storeId, new Date().getFullYear().toString()], 3014 3176 (err, result) => { … … 3141 3303 3142 3304 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', 3144 3306 [storeId, now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0')], 3145 3307 (err, result) => { … … 3199 3361 3200 3362 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', 3202 3364 [refundData.order_num], 3203 3365 (err, result) => { … … 3214 3376 3215 3377 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)', 3217 3379 [now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0')], 3218 3380 (err, result) => { … … 3264 3426 3265 3427 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', 3267 3429 [personalId], 3268 3430 (err, bossStore) => { … … 3282 3444 3283 3445 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', 3285 3447 [personalId, storeId], 3286 3448 (err, ownsStore) => { … … 3291 3453 } 3292 3454 3293 // FIXED: Changed SQL syntax from SUBSTRING(code FROM 4) to SUBSTR(code, 4) for SQLite compatibility3455 // PostgreSQL: extract the numeric product sequence from the store-prefixed product code 3294 3456 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', 3296 3458 [storeId], 3297 3459 (err, result) => { … … 3362 3524 3363 3525 database.database.get( 3364 'SELECT store_id FROM product WHERE code = ?',3526 'SELECT LEFT(code,3) AS store_id FROM product WHERE code = $1', 3365 3527 [productData.code], 3366 3528 (err, product) => { … … 3372 3534 3373 3535 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', 3375 3537 [personalId, product.store_id], 3376 3538 (err, ownsStore) => { … … 3499 3661 3500 3662 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', 3502 3664 [hashedPassword, userId], 3503 3665 function(err) { … … 3511 3673 // Also update password in personal table if it exists (for admin) 3512 3674 database.database.run( 3513 'UPDATE personal SET password = ? WHERE id = ?',3675 'UPDATE personal SET password = $1 WHERE id = $2', 3514 3676 [hashedPassword, userId], 3515 3677 function(err) { … … 3585 3747 3586 3748 database.database.run( 3587 'UPDATE personal SET password = ? WHERE id = ?',3749 'UPDATE personal SET password = $1 WHERE id = $2', 3588 3750 [hashedPassword, userId], 3589 3751 function(err) { … … 3597 3759 // Also update in users table if exists 3598 3760 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', 3600 3762 [hashedPassword, personal.email], 3601 3763 function(err) { … … 3608 3770 // Determine user type (boss/owner or employee) 3609 3771 database.database.get( 3610 'SELECT boss_id FROM boss WHERE boss_id = ?',3772 'SELECT boss_id FROM boss WHERE boss_id = $1', 3611 3773 [userId], 3612 3774 (err, boss) => { … … 3684 3846 3685 3847 database.database.get( 3686 'SELECT boss_id FROM boss WHERE boss_id = ?',3848 'SELECT boss_id FROM boss WHERE boss_id = $1', 3687 3849 [personalId], 3688 3850 (err, boss) => { … … 3790 3952 3791 3953 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)', 3793 3955 [ 3794 3956 newPersonalId, … … 3818 3980 3819 3981 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)', 3821 3983 [newPersonalId, dateOfHire], 3822 3984 (err) => { … … 3830 3992 3831 3993 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)', 3833 3995 [newPersonalId, storeId], 3834 3996 (err) => { … … 3842 4004 3843 4005 database.database.run( 3844 'INSERT INTO permissions (personal_id, type, authorisation) VALUES ( ?, ?, ?)',4006 'INSERT INTO permissions (personal_id, type, authorisation) VALUES ($1, $2, $3)', 3845 4007 [newPersonalId, 'EMPLOYEE', 'limited_access'], 3846 4008 (err) => { … … 3851 4013 // Also create entry in users table for login with force_password_change = 1 3852 4014 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)', 3854 4016 [ 3855 4017 newPersonalId, … … 3924 4086 3925 4087 database.database.get( 3926 'SELECT boss_id FROM boss WHERE boss_id = ?',4088 'SELECT boss_id FROM boss WHERE boss_id = $1', 3927 4089 [personalId], 3928 4090 (err, boss) => { … … 3947 4109 3948 4110 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', 3950 4112 [personalId, storeId], 3951 4113 (err, bossStore) => { … … 3957 4119 3958 4120 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', 3960 4122 [employeeId, storeId], 3961 4123 (err, employeeStore) => { … … 3967 4129 3968 4130 database.database.get( 3969 'SELECT boss_id FROM boss WHERE boss_id = ?',4131 'SELECT boss_id FROM boss WHERE boss_id = $1', 3970 4132 [employeeId], 3971 4133 (err, isBoss) => { … … 3989 4151 3990 4152 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', 3992 4154 [employeeId, storeId], 3993 4155 (err) => { … … 4001 4163 4002 4164 database.database.run( 4003 'DELETE FROM employees WHERE employee_id = ?',4165 'DELETE FROM employees WHERE employee_id = $1', 4004 4166 [employeeId], 4005 4167 (err) => { … … 4009 4171 4010 4172 database.database.run( 4011 'DELETE FROM permissions WHERE personal_id = ?',4173 'DELETE FROM permissions WHERE personal_id = $1', 4012 4174 [employeeId], 4013 4175 (err) => { … … 4017 4179 4018 4180 database.database.run( 4019 'DELETE FROM personal WHERE id = ?',4181 'DELETE FROM personal WHERE id = $1', 4020 4182 [employeeId], 4021 4183 (err) => { … … 4026 4188 // Also delete from users table 4027 4189 database.database.run( 4028 'DELETE FROM users WHERE id = ?',4190 'DELETE FROM users WHERE id = $1', 4029 4191 [employeeId], 4030 4192 (err) => { … … 4093 4255 4094 4256 database.database.get( 4095 'SELECT boss_id FROM boss WHERE boss_id = ?',4257 'SELECT boss_id FROM boss WHERE boss_id = $1', 4096 4258 [personalId], 4097 4259 (err, boss) => { … … 4116 4278 4117 4279 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', 4119 4281 [personalId, storeId], 4120 4282 (err, bossStore) => { … … 4126 4288 4127 4289 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', 4129 4291 [employeeId, storeId], 4130 4292 (err, employeeStore) => { … … 4150 4312 4151 4313 database.database.run( 4152 'UPDATE permissions SET type = ?, authorisation = ? WHERE personal_id = ?',4314 'UPDATE permissions SET type = $1, authorisation = $2 WHERE personal_id = $3', 4153 4315 [permissionType, authorization, employeeId], 4154 4316 function(err) { … … 4199 4361 4200 4362 database.database.get( 4201 'SELECT boss_id FROM boss WHERE boss_id = ?',4363 'SELECT boss_id FROM boss WHERE boss_id = $1', 4202 4364 [personalId], 4203 4365 (err, boss) => { … … 4222 4384 4223 4385 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', 4225 4387 [personalId, storeId], 4226 4388 (err, bossStore) => { … … 4232 4394 4233 4395 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', 4235 4397 [employeeId, storeId], 4236 4398 (err, employeeStore) => { … … 4245 4407 4246 4408 if (firstName) { 4247 updates.push( 'first_name = ?');4409 updates.push(`first_name = $${params.length + 1}`); 4248 4410 params.push(firstName); 4249 4411 } 4250 4412 4251 4413 if (lastName) { 4252 updates.push( 'last_name = ?');4414 updates.push(`last_name = $${params.length + 1}`); 4253 4415 params.push(lastName); 4254 4416 } … … 4260 4422 return; 4261 4423 } 4262 updates.push( 'email = ?');4424 updates.push(`email = $${params.length + 1}`); 4263 4425 params.push(email); 4264 4426 } … … 4273 4435 4274 4436 database.database.run( 4275 `UPDATE personal SET ${updates.join(', ')} WHERE id = ?`,4437 `UPDATE personal SET ${updates.join(', ')} WHERE id = $${params.length}`, 4276 4438 params, 4277 4439 function(err) { … … 4286 4448 if (email) { 4287 4449 database.database.run( 4288 'UPDATE users SET email = ? WHERE id = ?',4450 'UPDATE users SET email = $1 WHERE id = $2', 4289 4451 [email, employeeId], 4290 4452 (err) => { … … 4298 4460 if (firstName || lastName) { 4299 4461 database.database.get( 4300 'SELECT first_name, last_name FROM personal WHERE id = ?',4462 'SELECT first_name, last_name FROM personal WHERE id = $1', 4301 4463 [employeeId], 4302 4464 (err, personal) => { … … 4304 4466 const newUsername = `${personal.first_name} ${personal.last_name}`; 4305 4467 database.database.run( 4306 'UPDATE users SET username = ? WHERE id = ?',4468 'UPDATE users SET username = $1 WHERE id = $2', 4307 4469 [newUsername, employeeId], 4308 4470 (err) => { … … 4342 4504 if (!storeId) { 4343 4505 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', 4345 4507 [personalId], 4346 4508 (err, store) => { … … 4367 4529 4368 4530 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', 4370 4532 [personalId, storeId], 4371 4533 (err, ownsStore) => { … … 4396 4558 if (!storeId) { 4397 4559 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', 4399 4561 [personalId], 4400 4562 (err, store) => { … … 4421 4583 4422 4584 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', 4424 4586 [personalId, storeId], 4425 4587 (err, ownsStore) => { … … 4450 4612 if (!storeId) { 4451 4613 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', 4453 4615 [personalId], 4454 4616 (err, store) => { … … 4475 4637 4476 4638 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', 4478 4640 [personalId, storeId], 4479 4641 (err, ownsStore) => { … … 4504 4666 if (!storeId) { 4505 4667 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', 4507 4669 [personalId], 4508 4670 (err, store) => { … … 4529 4691 4530 4692 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', 4532 4694 [personalId, storeId], 4533 4695 (err, ownsStore) => { … … 4558 4720 if (!storeId) { 4559 4721 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', 4561 4723 [personalId], 4562 4724 (err, store) => { … … 4583 4745 4584 4746 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', 4586 4748 [personalId, storeId], 4587 4749 (err, ownsStore) => { … … 4684 4846 4685 4847 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', 4687 4849 [personalId, storeId], 4688 4850 (err, ownsStore) => { … … 4753 4915 4754 4916 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', 4756 4918 [personalId, storeId], 4757 4919 (err, ownsStore) => { … … 4765 4927 4766 4928 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'], 4769 4931 function(err) { 4770 4932 if (err) { -
view-database.js
r6fea37e r6149556 3 3 4 4 const dbPath = path.join(__dirname, 'database', 'handcraft.db'); 5 const db = new sqlite3.Database(dbPath); 5 const db = new sqlite3.Database(dbPath, (err) => { 6 if (err) { 7 console.error('Error opening database:', err.message); 8 process.exit(1); 9 } 10 }); 6 11 7 12 console.log('\nšØ HANDCRAFT MARKETPLACE - DATABASE CONTENTS\n'); … … 25 30 } 26 31 32 // Main execution chain 33 function 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 27 66 // 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 }); 67 function 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 } 57 100 58 101 // Display PERSONAL table 59 function checkPersonal( ) {102 function checkPersonal(next) { 60 103 console.log('\nš PERSONAL TABLE (STORE OWNERS/EMPLOYEES):'); 61 104 console.log('================================================================================'); 105 62 106 tableExists('personal', (exists) => { 63 107 if (!exists) { 64 console.log('Table does not exist ');65 checkStore();108 console.log('Table does not exist\n'); 109 if (next) next(); 66 110 return; 67 111 } … … 77 121 `, [], (err, rows) => { 78 122 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'); 86 130 } else { 87 131 console.log(`Total: ${rows.length} personal records\n`); … … 91 135 if (index < rows.length - 1) console.log('-'.repeat(80)); 92 136 }); 93 } 94 checkStore(); 137 console.log(); 138 } 139 if (next) next(); 95 140 }); 96 141 }); … … 98 143 99 144 // Display STORE table 100 function checkStore( ) {145 function checkStore(next) { 101 146 console.log('\nšŖ STORE TABLE:'); 102 147 console.log('================================================================================'); 148 103 149 tableExists('store', (exists) => { 104 150 if (!exists) { 105 console.log('Table does not exist ');106 checkProduct();151 console.log('Table does not exist\n'); 152 if (next) next(); 107 153 return; 108 154 } … … 110 156 db.all('SELECT * FROM store', [], (err, rows) => { 111 157 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'); 119 165 } else { 120 166 console.log(`Total: ${rows.length} stores\n`); … … 126 172 if (index < rows.length - 1) console.log('-'.repeat(80)); 127 173 }); 128 } 129 checkProduct(); 174 console.log(); 175 } 176 if (next) next(); 130 177 }); 131 178 }); … … 133 180 134 181 // Display PRODUCT table 135 function checkProduct( ) {182 function checkProduct(next) { 136 183 console.log('\nšļø PRODUCT TABLE:'); 137 184 console.log('================================================================================'); 185 138 186 tableExists('product', (exists) => { 139 187 if (!exists) { 140 console.log('Table does not exist ');141 checkCategory();188 console.log('Table does not exist\n'); 189 if (next) next(); 142 190 return; 143 191 } … … 149 197 `, [], (err, rows) => { 150 198 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'); 158 206 } else { 159 207 console.log(`Total: ${rows.length} products\n`); … … 165 213 if (index < rows.length - 1) console.log('-'.repeat(80)); 166 214 }); 167 } 168 checkCategory(); 215 console.log(); 216 } 217 if (next) next(); 169 218 }); 170 219 }); … … 172 221 173 222 // Display CATEGORY table 174 function checkCategory( ) {223 function checkCategory(next) { 175 224 console.log('\nš CATEGORY TABLE:'); 176 225 console.log('================================================================================'); 226 177 227 tableExists('category', (exists) => { 178 228 if (!exists) { 179 console.log('Table does not exist ');180 checkWorksInStore();229 console.log('Table does not exist\n'); 230 if (next) next(); 181 231 return; 182 232 } … … 188 238 `, [], (err, rows) => { 189 239 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'); 197 247 } else { 198 248 console.log(`Total: ${rows.length} categories\n`); … … 203 253 if (index < rows.length - 1) console.log('-'.repeat(80)); 204 254 }); 205 } 206 checkWorksInStore(); 255 console.log(); 256 } 257 if (next) next(); 207 258 }); 208 259 }); … … 210 261 211 262 // Display WORKS_IN_STORE table 212 function checkWorksInStore( ) {263 function checkWorksInStore(next) { 213 264 console.log('\nš WORKS_IN_STORE TABLE:'); 214 265 console.log('================================================================================'); 266 215 267 tableExists('works_in_store', (exists) => { 216 268 if (!exists) { 217 console.log('Table does not exist ');218 checkPermissions();269 console.log('Table does not exist\n'); 270 if (next) next(); 219 271 return; 220 272 } … … 227 279 `, [], (err, rows) => { 228 280 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'); 236 288 } else { 237 289 console.log(`Total: ${rows.length} assignments\n`); … … 241 293 if (index < rows.length - 1) console.log('-'.repeat(80)); 242 294 }); 243 } 244 checkPermissions(); 295 console.log(); 296 } 297 if (next) next(); 245 298 }); 246 299 }); … … 248 301 249 302 // Display PERMISSIONS table 250 function checkPermissions( ) {303 function checkPermissions(next) { 251 304 console.log('\nš PERMISSIONS TABLE:'); 252 305 console.log('================================================================================'); 306 253 307 tableExists('permissions', (exists) => { 254 308 if (!exists) { 255 console.log('Table does not exist ');256 checkEmployees();309 console.log('Table does not exist\n'); 310 if (next) next(); 257 311 return; 258 312 } … … 264 318 `, [], (err, rows) => { 265 319 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'); 273 327 } else { 274 328 console.log(`Total: ${rows.length} permissions\n`); … … 278 332 if (index < rows.length - 1) console.log('-'.repeat(80)); 279 333 }); 280 } 281 checkEmployees(); 334 console.log(); 335 } 336 if (next) next(); 282 337 }); 283 338 }); … … 285 340 286 341 // Display EMPLOYEES table 287 function checkEmployees( ) {342 function checkEmployees(next) { 288 343 console.log('\nš· EMPLOYEES TABLE:'); 289 344 console.log('================================================================================'); 345 290 346 tableExists('employees', (exists) => { 291 347 if (!exists) { 292 console.log('Table does not exist ');293 checkBoss();348 console.log('Table does not exist\n'); 349 if (next) next(); 294 350 return; 295 351 } … … 301 357 `, [], (err, rows) => { 302 358 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'); 310 366 } else { 311 367 console.log(`Total: ${rows.length} employees\n`); … … 315 371 if (index < rows.length - 1) console.log('-'.repeat(80)); 316 372 }); 317 } 318 checkBoss(); 373 console.log(); 374 } 375 if (next) next(); 319 376 }); 320 377 }); … … 322 379 323 380 // Display BOSS table 324 function checkBoss( ) {381 function checkBoss(next) { 325 382 console.log('\nš BOSS TABLE:'); 326 383 console.log('================================================================================'); 384 327 385 tableExists('boss', (exists) => { 328 386 if (!exists) { 329 console.log('Table does not exist ');330 checkOrder();387 console.log('Table does not exist\n'); 388 if (next) next(); 331 389 return; 332 390 } … … 338 396 `, [], (err, rows) => { 339 397 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'); 347 405 } else { 348 406 console.log(`Total: ${rows.length} bosses\n`); … … 352 410 if (index < rows.length - 1) console.log('-'.repeat(80)); 353 411 }); 354 } 355 checkOrder(); 412 console.log(); 413 } 414 if (next) next(); 356 415 }); 357 416 }); … … 359 418 360 419 // Display ORDER table 361 function checkOrder( ) {420 function checkOrder(next) { 362 421 console.log('\nš¦ ORDERS TABLE:'); 363 422 console.log('================================================================================'); 423 364 424 tableExists('order', (exists) => { 365 425 if (!exists) { 366 console.log('Table does not exist ');367 checkReport();426 console.log('Table does not exist\n'); 427 if (next) next(); 368 428 return; 369 429 } … … 376 436 `, [], (err, rows) => { 377 437 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'); 385 445 } else { 386 446 console.log(`Total: ${rows.length} orders\n`); … … 392 452 if (index < rows.length - 1) console.log('-'.repeat(80)); 393 453 }); 394 } 395 checkReport(); 454 console.log(); 455 } 456 if (next) next(); 396 457 }); 397 458 }); … … 399 460 400 461 // Display REPORT table 401 function checkReport( ) {462 function checkReport(next) { 402 463 console.log('\nš REPORTS TABLE:'); 403 464 console.log('================================================================================'); 465 404 466 tableExists('report', (exists) => { 405 467 if (!exists) { 406 console.log('Table does not exist ');407 checkRefund();468 console.log('Table does not exist\n'); 469 if (next) next(); 408 470 return; 409 471 } … … 411 473 db.all('SELECT * FROM report', [], (err, rows) => { 412 474 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'); 420 482 } else { 421 483 console.log(`Total: ${rows.length} reports\n`); … … 425 487 if (index < rows.length - 1) console.log('-'.repeat(80)); 426 488 }); 427 } 428 checkRefund(); 489 console.log(); 490 } 491 if (next) next(); 429 492 }); 430 493 }); … … 432 495 433 496 // Display REFUND table 434 function checkRefund( ) {497 function checkRefund(next) { 435 498 console.log('\nš° REFUND TABLE:'); 436 499 console.log('================================================================================'); 500 437 501 tableExists('refund', (exists) => { 438 502 if (!exists) { 439 console.log('Table does not exist ');440 checkImage();503 console.log('Table does not exist\n'); 504 if (next) next(); 441 505 return; 442 506 } … … 444 508 db.all('SELECT * FROM refund', [], (err, rows) => { 445 509 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'); 453 517 } else { 454 518 console.log(`Total: ${rows.length} refunds\n`); … … 459 523 if (index < rows.length - 1) console.log('-'.repeat(80)); 460 524 }); 461 } 462 checkImage(); 525 console.log(); 526 } 527 if (next) next(); 463 528 }); 464 529 }); … … 466 531 467 532 // Display IMAGE table 468 function checkImage( ) {533 function checkImage(next) { 469 534 console.log('\nš¼ļø IMAGE TABLE:'); 470 535 console.log('================================================================================'); 536 471 537 tableExists('image', (exists) => { 472 538 if (!exists) { 473 console.log('Table does not exist ');474 checkColor();539 console.log('Table does not exist\n'); 540 if (next) next(); 475 541 return; 476 542 } … … 478 544 db.all('SELECT * FROM image', [], (err, rows) => { 479 545 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'); 487 553 } else { 488 554 console.log(`Total: ${rows.length} images\n`); … … 491 557 if (index < rows.length - 1) console.log('-'.repeat(80)); 492 558 }); 493 } 494 checkColor(); 559 console.log(); 560 } 561 if (next) next(); 495 562 }); 496 563 }); … … 498 565 499 566 // Display COLOR table 500 function checkColor( ) {567 function checkColor(next) { 501 568 console.log('\nšØ COLOR TABLE:'); 502 569 console.log('================================================================================'); 570 503 571 tableExists('color', (exists) => { 504 572 if (!exists) { 505 console.log('Table does not exist ');506 finish();573 console.log('Table does not exist\n'); 574 if (next) next(); 507 575 return; 508 576 } … … 510 578 db.all('SELECT * FROM color', [], (err, rows) => { 511 579 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'); 519 587 } else { 520 588 console.log(`Total: ${rows.length} colors\n`); … … 523 591 if (index < rows.length - 1) console.log('-'.repeat(80)); 524 592 }); 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 536 601 function finish() { 537 console.log(' \n' + '='.repeat(80));602 console.log('='.repeat(80)); 538 603 console.log('ā Database inspection complete'); 539 604 console.log('='.repeat(80) + '\n'); 540 605 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 614 executeChecks();
Note:
See TracChangeset
for help on using the changeset viewer.
