From 27df1a73b52debe5d8712356baada7e8c7afb875 Mon Sep 17 00:00:00 2001 From: count-null <70529195+count-null@users.noreply.github.com> Date: Sat, 15 Feb 2025 00:21:37 -0500 Subject: [PATCH] save --- .env.example | 6 - composer.json | 3 +- composer.lock | 825 ++++++++++++++++++++++++++++++-- public/index.php | 13 + src/app.php | 4 + src/controllers/account.php | 92 +++- src/controllers/magic_link.php | 65 +-- src/models/addresses.php | 58 ++- src/models/cart_items.php | 53 ++ src/models/carts.php | 42 +- src/models/magic_links.php | 80 +++- src/models/order_items.php | 61 +++ src/models/orders.php | 67 ++- src/models/products.php | 63 ++- src/models/quote_items.php | 70 +++ src/models/quotes.php | 69 +++ src/models/subscriptions.php | 101 ++++ src/models/transactions.php | 82 +++- src/models/user_addresses.php | 16 +- src/models/users.php | 71 ++- src/style.css | 29 -- src/views/account/billing.twig | 4 +- src/views/account/index.twig | 8 +- src/views/account/login.twig | 2 +- src/views/account/shipping.twig | 4 +- src/views/account/signup.twig | 11 +- src/views/header.twig | 31 ++ src/views/lib/form/address.twig | 12 +- 28 files changed, 1695 insertions(+), 247 deletions(-) create mode 100644 src/models/cart_items.php create mode 100644 src/models/order_items.php create mode 100644 src/models/quote_items.php create mode 100644 src/models/quotes.php create mode 100644 src/models/subscriptions.php diff --git a/.env.example b/.env.example index 9191d51..16cc6ca 100644 --- a/.env.example +++ b/.env.example @@ -11,9 +11,3 @@ SMTP_FROM="noreply@example.com" # !! Choose your LN_SERVICE carefully!! # NOTE: The LN_SERVICE must support LUD-21 Payment Verification LN_ADDRESS="your@node.win" -# maps.co for GeoCoder (postal address verification) -# Get your free API key: https://geocode.maps.co -GEOCODE_MAPS_CO_API_KEY="your-api-key-from-geocode.maps.co" -# Plausible for privacy-respecting page analytics -# https://github.com/plausible/analytics -PLAUSIBLE_HOST="https://plausible.io/" \ No newline at end of file diff --git a/composer.json b/composer.json index 96eee48..654ed34 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "phpmailer/phpmailer": "^6.9.2", "vlucas/phpdotenv": "^5.6", "web-auth/webauthn-lib": "^5.0", - "twig/twig": "^3.0" + "twig/twig": "^3.0", + "swentel/nostr-php": "^1.5" } } diff --git a/composer.lock b/composer.lock index f70a913..bc0d4d2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,54 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ea128e458544f87060ad53880489fcc2", + "content-hash": "408b4a1daa73232eabf14c566a3e5d8d", "packages": [ + { + "name": "bitwasp/bech32", + "version": "v0.0.1", + "source": { + "type": "git", + "url": "https://github.com/Bit-Wasp/bech32.git", + "reference": "e1ea58c848a4ec59d81b697b3dfe9cc99968d0e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bit-Wasp/bech32/zipball/e1ea58c848a4ec59d81b697b3dfe9cc99968d0e7", + "reference": "e1ea58c848a4ec59d81b697b3dfe9cc99968d0e7", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.0", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/bech32.php" + ], + "psr-4": { + "BitWasp\\Bech32\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Unlicense" + ], + "authors": [ + { + "name": "Thomas Kerin", + "homepage": "https://thomaskerin.io", + "role": "Author" + } + ], + "description": "Pure (no dependencies) implementation of bech32", + "homepage": "https://github.com/bit-wasp/bech32", + "support": { + "issues": "https://github.com/Bit-Wasp/bech32/issues", + "source": "https://github.com/Bit-Wasp/bech32/tree/more-tests" + }, + "time": "2018-02-05T22:23:47+00:00" + }, { "name": "brick/math", "version": "0.12.1", @@ -618,6 +664,239 @@ }, "time": "2024-10-13T11:29:49+00:00" }, + { + "name": "phrity/net-stream", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-net-stream.git", + "reference": "e6ace997168bebcce814c95cd5c78c78663ae49a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-net-stream/zipball/e6ace997168bebcce814c95cd5c78c78663ae49a", + "reference": "e6ace997168bebcce814c95cd5c78c78663ae49a", + "shasum": "" + }, + "require": { + "php": "^8.0", + "phrity/util-errorhandler": "^1.1", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 | ^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "phrity/net-uri": "^2.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Net\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "Socket stream classes implementing PSR-7 Stream and PSR-17 StreamFactory", + "homepage": "https://phrity.sirn.se/net-stream", + "keywords": [ + "Socket", + "client", + "psr-17", + "psr-7", + "server", + "stream", + "stream factory" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-net-stream/issues", + "source": "https://github.com/sirn-se/phrity-net-stream/tree/2.1.2" + }, + "time": "2025-01-09T08:07:34+00:00" + }, + { + "name": "phrity/net-uri", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-net-uri.git", + "reference": "841190135af4fab18135226aaaf99ec5791377ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-net-uri/zipball/841190135af4fab18135226aaaf99ec5791377ac", + "reference": "841190135af4fab18135226aaaf99ec5791377ac", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 | ^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "phrity/util-errorhandler": "^1.1", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-intl": "Enables IDN conversion for non-ASCII domains" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Net\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "PSR-7 Uri and PSR-17 UriFactory implementation", + "homepage": "https://phrity.sirn.se/net-uri", + "keywords": [ + "psr-17", + "psr-7", + "uri", + "uri factory" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-net-uri/issues", + "source": "https://github.com/sirn-se/phrity-net-uri/tree/2.1.0" + }, + "time": "2024-07-08T06:14:09+00:00" + }, + { + "name": "phrity/util-errorhandler", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-util-errorhandler.git", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/483228156e06673963902b1cc1e6bd9541ab4d5e", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e", + "shasum": "" + }, + "require": { + "php": "^7.4 | ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Util\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "Inline error handler; catch and resolve errors for code block.", + "homepage": "https://phrity.sirn.se/util-errorhandler", + "keywords": [ + "error", + "warning" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-util-errorhandler/issues", + "source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.1.1" + }, + "time": "2024-09-12T06:49:16+00:00" + }, + { + "name": "phrity/websocket", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/websocket-php.git", + "reference": "6dceb42be8bfd500806a5508721ae14cf0d4d737" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/websocket-php/zipball/6dceb42be8bfd500806a5508721ae14cf0d4d737", + "reference": "6dceb42be8bfd500806a5508721ae14cf0d4d737", + "shasum": "" + }, + "require": { + "php": "^8.1", + "phrity/net-stream": "^2.1", + "phrity/net-uri": "^2.1", + "psr/http-message": "^1.1 | ^2.0", + "psr/log": "^1.0 | ^2.0 | ^3.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.0 | ^11.0", + "phrity/net-mock": "^2.1", + "phrity/util-errorhandler": "^1.1", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "WebSocket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Fredrik Liljegren" + }, + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "WebSocket client and server", + "homepage": "https://phrity.sirn.se/websocket", + "keywords": [ + "client", + "server", + "websocket" + ], + "support": { + "issues": "https://github.com/sirn-se/websocket-php/issues", + "source": "https://github.com/sirn-se/websocket-php/tree/3.2.2" + }, + "time": "2025-01-10T09:41:26+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -769,6 +1048,114 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, { "name": "psr/log", "version": "3.0.2", @@ -819,6 +1206,153 @@ }, "time": "2024-09-11T13:17:53+00:00" }, + { + "name": "simplito/bigint-wrapper-php", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/simplito/bigint-wrapper-php.git", + "reference": "cf21ec76d33f103add487b3eadbd9f5033a25930" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/simplito/bigint-wrapper-php/zipball/cf21ec76d33f103add487b3eadbd9f5033a25930", + "reference": "cf21ec76d33f103add487b3eadbd9f5033a25930", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "BI\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Simplito Team", + "email": "s.smyczynski@simplito.com", + "homepage": "https://simplito.com" + } + ], + "description": "Common interface for php_gmp and php_bcmath modules", + "support": { + "issues": "https://github.com/simplito/bigint-wrapper-php/issues", + "source": "https://github.com/simplito/bigint-wrapper-php/tree/1.0.0" + }, + "time": "2018-02-27T12:38:08+00:00" + }, + { + "name": "simplito/bn-php", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/simplito/bn-php.git", + "reference": "83446756a81720eacc2ffb87ff97958431451fd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/simplito/bn-php/zipball/83446756a81720eacc2ffb87ff97958431451fd6", + "reference": "83446756a81720eacc2ffb87ff97958431451fd6", + "shasum": "" + }, + "require": { + "simplito/bigint-wrapper-php": "~1.0.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "BN\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Simplito Team", + "email": "s.smyczynski@simplito.com", + "homepage": "https://simplito.com" + } + ], + "description": "Big number implementation compatible with bn.js", + "support": { + "issues": "https://github.com/simplito/bn-php/issues", + "source": "https://github.com/simplito/bn-php/tree/1.1.4" + }, + "time": "2024-01-10T16:16:59+00:00" + }, + { + "name": "simplito/elliptic-php", + "version": "1.0.12", + "source": { + "type": "git", + "url": "https://github.com/simplito/elliptic-php.git", + "reference": "be321666781be2be2c89c79c43ffcac834bc8868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/simplito/elliptic-php/zipball/be321666781be2be2c89c79c43ffcac834bc8868", + "reference": "be321666781be2be2c89c79c43ffcac834bc8868", + "shasum": "" + }, + "require": { + "ext-gmp": "*", + "simplito/bn-php": "~1.1.0" + }, + "require-dev": { + "phpbench/phpbench": "@dev", + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Elliptic\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Simplito Team", + "email": "s.smyczynski@simplito.com", + "homepage": "https://simplito.com" + } + ], + "description": "Fast elliptic curve cryptography", + "homepage": "https://github.com/simplito/elliptic-php", + "keywords": [ + "Curve25519", + "ECDSA", + "Ed25519", + "EdDSA", + "cryptography", + "curve", + "curve25519-weier", + "ecc", + "ecdh", + "elliptic", + "nistp192", + "nistp224", + "nistp256", + "nistp384", + "nistp521", + "secp256k1" + ], + "support": { + "issues": "https://github.com/simplito/elliptic-php/issues", + "source": "https://github.com/simplito/elliptic-php/tree/1.0.12" + }, + "time": "2024-01-09T14:57:04+00:00" + }, { "name": "spomky-labs/cbor-php", "version": "3.1.0", @@ -1011,6 +1545,73 @@ ], "time": "2025-01-03T09:35:48+00:00" }, + { + "name": "swentel/nostr-php", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/nostrver-se/nostr-php.git", + "reference": "6ef1e05da3845e352593c7d119fc00e716b3321f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nostrver-se/nostr-php/zipball/6ef1e05da3845e352593c7d119fc00e716b3321f", + "reference": "6ef1e05da3845e352593c7d119fc00e716b3321f", + "shasum": "" + }, + "require": { + "bitwasp/bech32": "^0.0.1", + "ext-gmp": "*", + "ext-xml": "*", + "php": ">=8.1 <8.5", + "phrity/websocket": "^3.0", + "simplito/elliptic-php": "^1.0", + "uma/phpecc": "^0.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.51", + "phpunit/phpunit": "^10.5" + }, + "bin": [ + "bin/nostr-php" + ], + "type": "library", + "autoload": { + "psr-4": { + "swentel\\nostr\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Hagens", + "email": "info@sebastix.nl", + "homepage": "https://sebastix.nl", + "role": "Developer & maintainer" + }, + { + "name": "Kristof De Jaeger", + "homepage": "https://realize.be", + "role": "Original author" + } + ], + "description": "Nostr helper library for PHP", + "homepage": "https://nostr-php.dev", + "keywords": [ + "library", + "nostr" + ], + "support": { + "chat": "https://t.me/nostr_php", + "issue": "https://github.com/swentel/nostr-php/issues", + "issues": "https://github.com/nostrver-se/nostr-php/issues", + "source": "https://github.com/nostrver-se/nostr-php/tree/push" + }, + "time": "2025-01-04T21:46:58+00:00" + }, { "name": "symfony/clock", "version": "v7.2.0", @@ -1783,16 +2384,16 @@ }, { "name": "symfony/property-access", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276" + "reference": "b28732e315d81fbec787f838034de7d6c9b2b902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", - "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", + "url": "https://api.github.com/repos/symfony/property-access/zipball/b28732e315d81fbec787f838034de7d6c9b2b902", + "reference": "b28732e315d81fbec787f838034de7d6c9b2b902", "shasum": "" }, "require": { @@ -1839,7 +2440,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.2.0" + "source": "https://github.com/symfony/property-access/tree/v7.2.3" }, "funding": [ { @@ -1855,20 +2456,20 @@ "type": "tidelift" } ], - "time": "2024-09-26T12:28:35+00:00" + "time": "2025-01-17T10:56:55+00:00" }, { "name": "symfony/property-info", - "version": "v7.2.2", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "1dfeb0dac7a99f7b3be42db9ccc299c5a6483fcf" + "reference": "dedb118fd588a92f226b390250b384d25f4192fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/1dfeb0dac7a99f7b3be42db9ccc299c5a6483fcf", - "reference": "1dfeb0dac7a99f7b3be42db9ccc299c5a6483fcf", + "url": "https://api.github.com/repos/symfony/property-info/zipball/dedb118fd588a92f226b390250b384d25f4192fe", + "reference": "dedb118fd588a92f226b390250b384d25f4192fe", "shasum": "" }, "require": { @@ -1879,7 +2480,9 @@ "conflict": { "phpdocumentor/reflection-docblock": "<5.2", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<6.4" + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2", @@ -1922,7 +2525,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.2.2" + "source": "https://github.com/symfony/property-info/tree/v7.2.3" }, "funding": [ { @@ -1938,20 +2541,20 @@ "type": "tidelift" } ], - "time": "2024-12-31T11:04:50+00:00" + "time": "2025-01-27T11:08:17+00:00" }, { "name": "symfony/serializer", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0" + "reference": "320f30beb419ce4f96363ada5e225c41f1ef08ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", - "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", + "url": "https://api.github.com/repos/symfony/serializer/zipball/320f30beb419ce4f96363ada5e225c41f1ef08ab", + "reference": "320f30beb419ce4f96363ada5e225c41f1ef08ab", "shasum": "" }, "require": { @@ -2020,7 +2623,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.2.0" + "source": "https://github.com/symfony/serializer/tree/v7.2.3" }, "funding": [ { @@ -2036,7 +2639,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T15:21:05+00:00" + "time": "2025-01-29T07:13:55+00:00" }, { "name": "symfony/string", @@ -2276,16 +2879,16 @@ }, { "name": "twig/twig", - "version": "v3.18.0", + "version": "v3.19.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50" + "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", - "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/d4f8c2b86374f08efc859323dbcd95c590f7124e", + "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e", "shasum": "" }, "require": { @@ -2340,7 +2943,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.18.0" + "source": "https://github.com/twigphp/Twig/tree/v3.19.0" }, "funding": [ { @@ -2352,7 +2955,165 @@ "type": "tidelift" } ], - "time": "2024-12-29T10:51:50+00:00" + "time": "2025-01-29T07:06:14+00:00" + }, + { + "name": "uma/phpasn1", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/1ma/PHPASN1.git", + "reference": "dd805d3157ddc90515ee13562a9bb241c68f4f88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/1ma/PHPASN1/zipball/dd805d3157ddc90515ee13562a9bb241c68f4f88", + "reference": "dd805d3157ddc90515ee13562a9bb241c68f4f88", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-bcmath": "BCmath is the fallback extension for big integer calculations", + "ext-curl": "For loading OID information from the web if they have not bee defined statically", + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "keywords": [ + "DER", + "asn.1", + "asn1", + "ber", + "binary", + "decoding", + "encoding", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "source": "https://github.com/1ma/PHPASN1/tree/v2.5.1" + }, + "abandoned": "genkgo/php-asn1", + "time": "2024-11-29T17:34:36+00:00" + }, + { + "name": "uma/phpecc", + "version": "v0.2.1", + "source": { + "type": "git", + "url": "https://github.com/1ma/phpecc.git", + "reference": "e269be5eaef099fb46831037d097c647a6da2f69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/1ma/phpecc/zipball/e269be5eaef099fb46831037d097c647a6da2f69", + "reference": "e269be5eaef099fb46831037d097c647a6da2f69", + "shasum": "" + }, + "require": { + "ext-gmp": "*", + "php": "^8.0", + "uma/phpasn1": "^2.5.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "squizlabs/php_codesniffer": "^2.0", + "symfony/yaml": "^6.4 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mdanter\\Ecc\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matyas Danter", + "homepage": "http://matejdanter.com/", + "role": "Author" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io", + "homepage": "http://aztech.io", + "role": "Maintainer" + }, + { + "name": "Thomas Kerin", + "email": "afk11@users.noreply.github.com", + "role": "Maintainer" + }, + { + "name": "Ology Newswire, Inc", + "email": "protocol@vpsqr.com", + "homepage": "https://vpsqr.com/", + "role": "Maintainer" + } + ], + "description": "Temporary fork of public-square/phpecc", + "homepage": "https://github.com/1ma/phpecc", + "keywords": [ + "Diffie", + "ECDSA", + "Hellman", + "curve", + "ecdh", + "elliptic", + "nistp192", + "nistp224", + "nistp256", + "nistp384", + "nistp521", + "phpecc", + "schnorr", + "secp256k1", + "secp256r1" + ], + "support": { + "source": "https://github.com/1ma/phpecc/tree/v0.2.1" + }, + "abandoned": "paragonie/ecc", + "time": "2025-01-21T00:38:50+00:00" }, { "name": "vlucas/phpdotenv", @@ -2522,16 +3283,16 @@ }, { "name": "web-auth/webauthn-lib", - "version": "5.0.1", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "2cc8262b885cf01eee3c4c10ca3985bdd2614c97" + "reference": "6b95b2b3902d943796c3c2bac2dd14af9d031fc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/2cc8262b885cf01eee3c4c10ca3985bdd2614c97", - "reference": "2cc8262b885cf01eee3c4c10ca3985bdd2614c97", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/6b95b2b3902d943796c3c2bac2dd14af9d031fc2", + "reference": "6b95b2b3902d943796c3c2bac2dd14af9d031fc2", "shasum": "" }, "require": { @@ -2592,7 +3353,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/5.0.1" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.1.1" }, "funding": [ { @@ -2604,7 +3365,7 @@ "type": "patreon" } ], - "time": "2024-07-20T05:24:59+00:00" + "time": "2025-01-03T23:01:20+00:00" }, { "name": "webmozart/assert", diff --git a/public/index.php b/public/index.php index 406eaac..4e4c1bf 100644 --- a/public/index.php +++ b/public/index.php @@ -21,19 +21,31 @@ Dotenv\Dotenv::createImmutable(__DIR__ . '/../')->load(); // Start the session app::init_db(); use app\models\addresses; +use app\models\cart_items; use app\models\carts; use app\models\magic_links; +use app\models\order_items; use app\models\orders; use app\models\products; +use app\models\quote_items; +use app\models\quotes; +use app\models\subscriptions; +use app\models\transactions; use app\models\user_addresses; use app\models\users; if (!app::$db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")->fetch()) { addresses::init(); + cart_items::init(); carts::init(); magic_links::init(); + order_items::init(); orders::init(); products::init(); + quote_items::init(); + quotes::init(); + subscriptions::init(); + transactions::init(); user_addresses::init(); users::init(); } @@ -140,6 +152,7 @@ $controller = match ($route) { '/account' => account::index($defaults), '/account/profile' => account::profile(), '/account/login' => account::login($defaults), + '/account/email' => account::email(), '/account/logout' => account::logout(), '/magic-link' => magic_link::index(), '/account/returns' => account::returns($defaults), diff --git a/src/app.php b/src/app.php index 3eef2ac..834a1c8 100644 --- a/src/app.php +++ b/src/app.php @@ -1,5 +1,6 @@ getMessage()); } } + public static function send_mail($to, $from, $from_name, $subject, $message, $HTML_message) { $mail = new PHPMailer(exceptions: true); @@ -40,6 +43,7 @@ class app $mail->send(); ob_end_clean(); } + public static function sendJson($data, $status = 200) { http_response_code($status); diff --git a/src/controllers/account.php b/src/controllers/account.php index cb13e1e..26ec594 100644 --- a/src/controllers/account.php +++ b/src/controllers/account.php @@ -4,6 +4,7 @@ namespace app\controllers; use app\models\addresses; use app\models\users; use app\models\user_addresses; +use app\models\magic_links; class account { @@ -12,8 +13,8 @@ class account if (!isset($_SESSION['user_id'])) { header('Location: /account/login'); } - $email = $_SESSION['user_email']; - $user = users::getByEmail($email); + $user_id = $_SESSION['user_id']; + $user = users::getById($user_id); $default_shipping = null; $default_billing = null; $ship_addrs = []; @@ -60,8 +61,8 @@ class account $bill_id = addresses::add( $bill['name'], $bill['company'], - $bill['street'], - $bill['boxapt'], + $bill['addressLine1'], + $bill['addressLine2'], $bill['city'], $bill['state'], $bill['zip'], @@ -76,8 +77,8 @@ class account $_SESSION['success'] = "Billing address saved!"; header('Location: /account/billing'); } - $email = $_SESSION['user_email']; - $user = users::getByEmail($email); + $user_id = $_SESSION['user_id']; + $user = users::getById($user_id); $default_billing = null; $bill_addrs = []; $bill_addresses = user_addresses::getBillingByUserId($_SESSION['user_id']); @@ -115,8 +116,42 @@ class account header('Location: /account'); } } + + public static function email() + { + $user_id = $_SESSION['user_id'] ?? null; + if (empty($user_id)){ + header('Location: /account/login'); + } + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $email = $_POST['email'] ?? null; + if (empty($email)) { + $_SESSION['error'] = "Enter your email to get a login link"; + header('Location: /account'); + exit; + } else { + $token = magic_links::add($email, $user_id); + users::updateReplaceEmailTokenById($user_id, $token); + header('Location: /account'); + exit; + } + } + } + public static function login($defaults) { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $email = $_POST['email'] ?? false; + if (empty($email)) { + $_SESSION['error'] = "Enter your email to get a login link"; + header('Location: /account/login'); + exit; + } else { + $token = magic_links::add($email, null); + header('Location: /account/login'); + exit; + } + } if (isset($_SESSION['user_id'])) { header('Location: /account'); } @@ -131,12 +166,14 @@ class account ] ])); } + public static function logout() { session_unset(); session_destroy(); header('Location: /'); } + public static function orders($defaults) { if (!isset($_SESSION['user_id'])) { @@ -185,8 +222,8 @@ class account $ship_id = addresses::add( $ship['name'], $ship['company'], - $ship['street'], - $ship['boxapt'], + $ship['addressLine1'], + $ship['addressLine2'], $ship['city'], $ship['state'], $ship['zip'], @@ -201,8 +238,8 @@ class account $_SESSION['success'] = "Shipping address saved!"; header('Location: /account/shipping'); } - $email = $_SESSION['user_email']; - $user = users::getByEmail($email); + $user_id = $_SESSION['user_id']; + $user = users::getById($user_id); $addresses = user_addresses::getShippingByUserId($user['id']); $default_shipping = null; $ship_addrs = []; @@ -235,27 +272,30 @@ class account { if ($_SERVER['REQUEST_METHOD'] == 'POST') { $email = $_POST['email']; + if (empty($email)) { + $_SESSION['error'] = 'Email is required.'; + } $existingUser = users::getByEmail($email); if ($existingUser) { $_SESSION['error'] = 'Email already exists. Please choose a different email or log in.'; + $_SESSION['last_post'] = $_POST; header('Location: /account/signup'); exit; } - if (empty($email)) { - $_SESSION['error'] = 'Email is required.'; - } - if (isset($_SESSION['error'])) { + $useShipping = $_POST['use_shipping'] ?? false; + $ship = addresses::validatePost("shipping"); + if (!isset($ship['name'])){ + $_SESSION['error'] = "Shipping address verification failed. Check your entry for errors."; + $_SESSION['last_post'] = $_POST; header('Location: /account/signup'); } - $useShipping = $_POST['use_shipping'] ?? false; - if ($useShipping) { - $ship = addresses::validatePost("shipping"); - } else { - $ship = addresses::validatePost("shipping"); + if (!$useShipping) { $bill = addresses::validatePost("billing"); - } - if (empty($email)) { - $_SESSION['error'] = 'Email is required.'; + if (!isset($bill['name'])){ + $_SESSION['error'] = "Billing address verification failed. Check your entry for errors."; + $_SESSION['last_post'] = $_POST; + header('Location: /account/signup'); + } } if (isset($_SESSION['error'])) { $_SESSION['last_post'] = $_POST; @@ -264,8 +304,8 @@ class account $ship_id = addresses::add( $ship['name'], $ship['company'], - $ship['street'], - $ship['boxapt'], + $ship['addressLine1'], + $ship['addressLine2'], $ship['city'], $ship['state'], $ship['zip'], @@ -278,8 +318,8 @@ class account $bill_id = addresses::add( $bill['name'], $bill['company'], - $bill['street'], - $bill['boxapt'], + $bill['addressLine1'], + $bill['addressLine2'], $bill['city'], $bill['state'], $bill['zip'], diff --git a/src/controllers/magic_link.php b/src/controllers/magic_link.php index 11af968..8b38d84 100644 --- a/src/controllers/magic_link.php +++ b/src/controllers/magic_link.php @@ -8,60 +8,45 @@ class magic_link { public static function index() { - $email = $_GET['email'] ?? null; $token = $_GET['token'] ?? null; - $signup = $_GET['signup'] ?? null; - - if (empty($email) && empty($token)) { - $_SESSION['error'] = "Enter your email to get a login link"; + if (!$token) { + $_SESSION['error'] = "Invalid or expired link."; header('Location: /account/login'); exit; - } - - if ($email && empty($token) && empty($signup)) { - $link = magic_links::add(email: $email); - $subject = "Your Magic Sign-In Link"; - $message = "Copy and paste the link into your browser: $link"; - $HTML_message = "Click the link to sign in: $link"; - app::send_mail(to: $email, from: $_ENV['SMTP_FROM'], from_name: $_ENV['APP_NAME'], subject: $subject, message: $message, HTML_message: $HTML_message); - $_SESSION['success'] = 'Link sent to your email!'; - header('Location: /account/login'); - exit; - } - - if ($email && empty($token) && $signup == "1") { - $link = magic_links::add(email: $email); - $subject = "Your Magic Sign-In Link"; - $message = "Copy and paste the link into your browser: $link"; - $HTML_message = "Click the link to sign in: $link"; - app::send_mail(to: $email, from: $_ENV['SMTP_FROM'], from_name: $_ENV['APP_NAME'], subject: $subject, message: $message, HTML_message: $HTML_message); - $_SESSION['success'] = 'Account created! Please check your email inbox for the verification link.'; - header('Location: /account/login'); - exit; - } - - if ($token && empty($email)) { - $link = magic_links::validate(token: $token); - + } else { + $link = magic_links::validateToken(token: $token); if (!$link) { $_SESSION['error'] = "Invalid or expired link."; header('Location: /account/login'); + exit; } - // handle signup vs. login - $user = users::getByEmail($link['email']); - if ($user) { + $user = $link['user_id'] ? users::getById($link['user_id']) : users::getByEmail($link['email']); + if ($user) { // user with this email exists, log them in $_SESSION['user_email'] = $link['email']; $_SESSION['user_id'] = $user['id']; if (!$user['verified']) { users::verify($link['email']); } header('Location: /account'); - } else { - // used to pre-fill email signup field - $_SESSION['user_email'] = $link['email']; - header('Location: /account/signup'); + exit; + } else { // no users with this email + $user_replacing_email = users::getByReplaceEmailToken($token); + if ($user_replacing_email) { // user is replacing their email + $user_id = $user_replacing_email['id']; + users::updateEmailById($user_id, $link['email']); + $_SESSION['user_email'] = $link['email']; + $_SESSION['user_id'] = $user_id; + if (!$user['verified']) { + users::verify($link['email']); + } + header('Location: /account'); + exit; + } else { // new user signup + $_SESSION['user_email'] = $link['email']; + header('Location: /account/signup'); + exit; + } } - exit(); } } } diff --git a/src/models/addresses.php b/src/models/addresses.php index e29adaa..ecbfb81 100644 --- a/src/models/addresses.php +++ b/src/models/addresses.php @@ -10,8 +10,8 @@ class addresses id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, company TEXT, - street TEXT NOT NULL, - boxapt TEXT NOT NULL, + addressLine1 TEXT NOT NULL, + addressLine2 TEXT NOT NULL, city TEXT NOT NULL, state TEXT NOT NULL, zip TEXT NOT NULL, @@ -25,36 +25,52 @@ class addresses { $name = $_POST["{$type}_name"]; $company = $_POST["{$type}_company"] ?? null; - $boxapt = $_POST["{$type}_boxapt"] ?? null; - $street = $_POST["{$type}_street"]; + $addressLine2 = $_POST["{$type}_addressLine2"] ?? null; + $addressLine1 = $_POST["{$type}_addressLine1"]; $city = $_POST["{$type}_city"]; $state = $_POST["{$type}_state"]; $zip = $_POST["{$type}_zip"]; $phone = $_POST["{$type}_phone"]; // check all required fields are set - if (empty($name) || empty($street) || empty($city) || empty($state) || empty($zip) || empty($phone)) { + if (empty($name) || empty($addressLine1) || empty($city) || empty($state) || empty($zip) || empty($phone)) { $_SESSION['error'] = "Missing required {$type} information."; } - // TODO: find a match using postal database and return that + $url = "https://nominatim.openstreetmap.org/search?" . http_build_query([ + "q" => implode(" ", array_filter([$addressLine1, $addressLine2, $city, $state, $zip])), + "format" => "json", + "addressdetails" => 1, + "limit" => 1 + ]); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERAGENT, "AddressValidator/1.0"); + $response = curl_exec($ch); + curl_close($ch); + $data = json_decode($response, true); + if (empty($data)) { + return ["error" => "Address not found"]; + } + $addressDetails = $data[0]['address']; return [ - 'name' => $name, - 'company' => $company, - 'street' => $street, - 'boxapt' => $boxapt, - 'city' => $city, - 'state' => $state, - 'zip' => $zip, - 'phone' => $phone + 'name' => $name, + 'company' => $company, + "addressLine1" => ($addressDetails['house_number'] ?? '') . ' ' . ($addressDetails['building'] ?? '') . ' ' . ($addressDetails['road'] ?? ''), + "addressLine2" => $addressLine2, + "city" => $addressDetails['city'] ?? $addressDetails['town'] ?? $addressDetails['village'] ?? '', + "state" => $addressDetails['state_code'] ?? ($addressDetails['state'] ?? ''), + "zip" => $addressDetails['postcode'] ?? '', + 'phone' => $phone ]; } - public static function add($name, $company, $street, $boxapt, $city, $state, $zip, $phone, $billing, $shipping) + public static function add($name, $company, $addressLine1, $addressLine2, $city, $state, $zip, $phone, $billing, $shipping) { $query = "INSERT INTO addresses ( name, company, - street, - boxapt, + addressLine1, + addressLine2, city, state, zip, @@ -64,8 +80,8 @@ class addresses ) VALUES ( :name, :company, - :street, - :boxapt, + :addressLine1, + :addressLine2, :city, :state, :zip, @@ -76,8 +92,8 @@ class addresses $stmt = app::$db->prepare($query); $stmt->bindParam(':name', $name); $stmt->bindParam(':company', $company); - $stmt->bindParam(':street', $street); - $stmt->bindParam(':boxapt', $boxapt); + $stmt->bindParam(':addressLine1', $addressLine1); + $stmt->bindParam(':addressLine2', $addressLine2); $stmt->bindParam(':city', $city); $stmt->bindParam(':state', $state); $stmt->bindParam(':zip', $zip); diff --git a/src/models/cart_items.php b/src/models/cart_items.php new file mode 100644 index 0000000..d12b485 --- /dev/null +++ b/src/models/cart_items.php @@ -0,0 +1,53 @@ +exec("CREATE TABLE IF NOT EXISTS cart_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cart_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + quantity INTEGER NOT NULL CHECK(quantity > 0), + added_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (cart_id) REFERENCES carts(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) + );"); + } + + public static function addItem(int $cartId, int $productId, int $quantity) + { + $stmt = app::$db->prepare("INSERT INTO cart_items (cart_id, product_id, quantity) + VALUES (:cart_id, :product_id, :quantity)"); + $stmt->execute([ + 'cart_id' => $cartId, + 'product_id' => $productId, + 'quantity' => $quantity + ]); + } + + public static function updateItem(int $cartItemId, int $quantity) + { + $stmt = app::$db->prepare("UPDATE cart_items SET quantity = :quantity WHERE cart_item_id = :cart_item_id"); + $stmt->execute([ + 'cart_item_id' => $cartItemId, + 'quantity' => $quantity + ]); + } + + public static function removeItem(int $cartItemId) + { + $stmt = app::$db->prepare("DELETE FROM cart_items WHERE cart_item_id = :cart_item_id"); + $stmt->execute(['cart_item_id' => $cartItemId]); + } + + public static function getCartItems(int $cartId): array + { + $stmt = app::$db->prepare("SELECT * FROM cart_items WHERE cart_id = :cart_id"); + $stmt->execute(['cart_id' => $cartId]); + return $stmt->fetchAll(); + } +} diff --git a/src/models/carts.php b/src/models/carts.php index d9cdc70..73b4813 100644 --- a/src/models/carts.php +++ b/src/models/carts.php @@ -2,20 +2,42 @@ namespace app\models; use app\app; + class carts { public static function init() { - app::$db->exec("CREATE TABLE IF NOT EXISTS order_items ( - order_item_id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - quantity INTEGER NOT NULL CHECK(quantity > 0), - price REAL NOT NULL CHECK(price >= 0), - FOREIGN KEY (order_id) REFERENCES orders(order_id), - FOREIGN KEY (product_id) REFERENCES products(product_id) + app::$db->exec("CREATE TABLE IF NOT EXISTS carts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + short_id TEXT UNIQUE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) );"); } + + public static function addCart(int $userId): string + { + return self::createCart($userId, 'user_cart_id'); + } + + public static function addSaved(int $userId): string + { + return self::createCart($userId, 'user_saved_for_later_id'); + } + + private static function createCart(int $userId, string $column): string + { + $characters = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ'; + $shortId = ''; + + for ($i = 0; $i < 6; $i++) { + $shortId .= $characters[random_int(0, strlen($characters) - 1)]; + } + + app::$db->prepare("INSERT INTO carts (user_id, short_id) VALUES (:user_id, :short_id)") + ->execute(['user_id' => $userId, 'short_id' => $shortId]); + + return $shortId; + } } - - diff --git a/src/models/magic_links.php b/src/models/magic_links.php index b70d4cc..83c89ac 100644 --- a/src/models/magic_links.php +++ b/src/models/magic_links.php @@ -8,38 +8,84 @@ class magic_links { app::$db->exec("CREATE TABLE IF NOT EXISTS magic_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER REFERENCES users(id), email TEXT NOT NULL, + code TEXT NOT NULL, token TEXT NOT NULL, expires_at DATETIME NOT NULL, used BOOLEAN DEFAULT FALSE )"); } - public static function add($email) + public static function add($email, $user_id) { + $code = str_pad(strval(random_int(0, 999999)), 6, "0", STR_PAD_LEFT); $token = bin2hex(random_bytes(32)); $expires_at = date('Y-m-d H:i:s', time() + 60 * 15); - app::$db->query("INSERT INTO magic_links ( - email, - token, + $query = "INSERT INTO magic_links ( + email, + user_id, + token, + code, expires_at ) VALUES ( - '$email', - '$token', - '$expires_at' - )"); - return $_ENV['APP_HOST'] . "/magic-link?token=" . urlencode($token); + :email, + :user_id, + :token, + :code, + :expires_at + )"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':email', $email); + $stmt->bindParam(':user_id', $user_id); + $stmt->bindParam(':token', $token); + $stmt->bindParam(':code', $code); + $stmt->bindParam(':expires_at', $expires_at); + $stmt->execute(); + $link = $_ENV['APP_HOST'] . "/magic-link?token=" . urlencode($token); + $subject = "Your Magic Sign-In Link"; + $message = "Enter this code into the sign-in form\n$code\n or copy-paste this link into your browser to sign in:\n$link"; + $HTML_message = "Click the link to sign in: $link or enter this code:
$code"; + app::send_mail(to: $email, from: $_ENV['SMTP_FROM'], from_name: $_ENV['APP_NAME'], subject: $subject, message: $message, HTML_message: $HTML_message); + $_SESSION['success'] = 'Link sent to your email!'; + return $token; } - public static function validate($token) + public static function validateToken($token) { - $link = app::$db->query("SELECT * FROM magic_links - WHERE token = '$token' - AND used = FALSE - AND expires_at > datetime('now') - ")->fetch(\PDO::FETCH_ASSOC); - // void the token once validated - app::$db->query("UPDATE magic_links SET used = TRUE WHERE token = '$token'"); + $query = "SELECT * FROM magic_links + WHERE token = :token + AND used = FALSE + AND expires_at > datetime('now')"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':token', $token); + $stmt->execute(); + $link = $stmt->fetch(\PDO::FETCH_ASSOC); + + $updateQuery = "UPDATE magic_links SET used = TRUE WHERE token = :token"; + $updateStmt = app::$db->prepare($updateQuery); + $updateStmt->bindParam(':token', $token); + $updateStmt->execute(); + + return $link; + } + + public static function validateCode($code) + { + $query = "SELECT * FROM magic_links + WHERE code = :code + AND used = FALSE + AND expires_at > datetime('now')"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':code', $code); + $stmt->execute(); + $link = $stmt->fetch(\PDO::FETCH_ASSOC); + + $updateQuery = "UPDATE magic_links SET used = TRUE WHERE code = :code"; + $updateStmt = app::$db->prepare($updateQuery); + $updateStmt->bindParam(':code', $code); + $updateStmt->execute(); + return $link; } } diff --git a/src/models/order_items.php b/src/models/order_items.php new file mode 100644 index 0000000..1d03b3e --- /dev/null +++ b/src/models/order_items.php @@ -0,0 +1,61 @@ +exec("CREATE TABLE IF NOT EXISTS order_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + quantity INTEGER NOT NULL CHECK(quantity > 0), + added_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) + );"); + } + + public static function addItem(int $orderId, int $productId, int $quantity) + { + if ($quantity <= 0) { + throw new \InvalidArgumentException('Quantity must be greater than zero.'); + } + + $stmt = app::$db->prepare("INSERT INTO order_items (order_id, product_id, quantity) + VALUES (:order_id, :product_id, :quantity)"); + $stmt->execute([ + 'order_id' => $orderId, + 'product_id' => $productId, + 'quantity' => $quantity + ]); + } + + public static function updateItem(int $orderItemId, int $quantity) + { + if ($quantity <= 0) { + throw new \InvalidArgumentException('Quantity must be greater than zero.'); + } + + $stmt = app::$db->prepare("UPDATE order_items SET quantity = :quantity WHERE order_item_id = :order_item_id"); + $stmt->execute([ + 'order_item_id' => $orderItemId, + 'quantity' => $quantity + ]); + } + + public static function removeItem(int $orderItemId) + { + $stmt = app::$db->prepare("DELETE FROM order_items WHERE order_item_id = :order_item_id"); + $stmt->execute(['order_item_id' => $orderItemId]); + } + + public static function getOrderItems(int $orderId): array + { + $stmt = app::$db->prepare("SELECT * FROM order_items WHERE order_id = :order_id"); + $stmt->execute(['order_id' => $orderId]); + return $stmt->fetchAll(); + } +} diff --git a/src/models/orders.php b/src/models/orders.php index 14b2296..0df971d 100644 --- a/src/models/orders.php +++ b/src/models/orders.php @@ -2,17 +2,72 @@ namespace app\models; use app\app; + class orders { + const STATUSES = [ + 'SHIPPED', 'PENDING', 'HOLD', 'PARTIAL', + 'BACKORDER', 'FAILED', 'CANCELED', 'PROCESSING' + ]; + public static function init() { app::$db->exec("CREATE TABLE IF NOT EXISTS orders ( - order_id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - total_amount REAL NOT NULL CHECK(total_amount >= 0), - status TEXT NOT NULL CHECK(status IN ('pending', 'completed', 'cancelled')), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(user_id) + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + value_sats INTEGER NOT NULL CHECK(value_sats >= 0), + value_cents INTEGER NOT NULL CHECK(value_cents >= 0), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + status TEXT CHECK(status IN ('" . implode("', '", self::STATUSES) . "')) NOT NULL DEFAULT 'PENDING', + FOREIGN KEY (user_id) REFERENCES users(id) );"); } + + public static function createOrder(int $userId, int $valueSats, int $valueCents, string $status = 'PENDING'): int + { + self::validateStatus($status); + + $stmt = app::$db->prepare("INSERT INTO orders (user_id, value_sats, value_cents, status) + VALUES (:user_id, :value_sats, :value_cents, :status)"); + $stmt->execute([ + 'user_id' => $userId, + 'value_sats' => $valueSats, + 'value_cents' => $valueCents, + 'status' => $status + ]); + + return app::$db->lastInsertId(); + } + + public static function updateStatus(int $orderId, string $status) + { + self::validateStatus($status); + + $stmt = app::$db->prepare("UPDATE orders SET status = :status WHERE order_id = :order_id"); + $stmt->execute([ + 'order_id' => $orderId, + 'status' => $status + ]); + } + + public static function getOrder(int $orderId): array + { + $stmt = app::$db->prepare("SELECT * FROM orders WHERE order_id = :order_id"); + $stmt->execute(['order_id' => $orderId]); + return $stmt->fetch() ?: []; + } + + public static function getUserOrders(int $userId): array + { + $stmt = app::$db->prepare("SELECT * FROM orders WHERE user_id = :user_id ORDER BY created_at DESC"); + $stmt->execute(['user_id' => $userId]); + return $stmt->fetchAll(); + } + + private static function validateStatus(string $status) + { + if (!in_array($status, self::STATUSES, true)) { + throw new \InvalidArgumentException("Invalid order status: $status"); + } + } } diff --git a/src/models/products.php b/src/models/products.php index 45bf7ab..985c19b 100644 --- a/src/models/products.php +++ b/src/models/products.php @@ -2,16 +2,69 @@ namespace app\models; use app\app; + class products { public static function init() { app::$db->exec("CREATE TABLE IF NOT EXISTS products ( - product_id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - description TEXT, - price REAL NOT NULL CHECK(price >= 0), - qty INTEGER NOT NULL DEFAULT 0 CHECK(qty >= 0) + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + desc TEXT, + stock_qty INTEGER NOT NULL DEFAULT 0 CHECK(stock_qty >= 0), + specs_json TEXT, + sats_price INTEGER NOT NULL DEFAULT 0 CHECK(sats_price >= 0), + cents_price INTEGER NOT NULL DEFAULT 0 CHECK(cents_price >= 0), + digital BOOLEAN NOT NULL DEFAULT 0, + subscription BOOLEAN NOT NULL DEFAULT 0, + image_url_0 TEXT, + image_url_1 TEXT, + image_url_2 TEXT, + image_url_3 TEXT, + image_url_4 TEXT, + image_url_5 TEXT, + image_url_6 TEXT, + image_url_7 TEXT, + image_url_8 TEXT, + image_url_9 TEXT, + image_url_10 TEXT, + image_url_11 TEXT )"); } + + public static function add($title, $desc, $stock_qty, $specs_json, $sats_price, $cents_price, $digital, $subscription, $images) + { + $stmt = app::$db->prepare("INSERT INTO products ( + title, desc, stock_qty, specs_json, sats_price, cents_price, digital, subscription, + image_url_0, image_url_1, image_url_2, image_url_3, image_url_4, image_url_5, + image_url_6, image_url_7, image_url_8, image_url_9, image_url_10, image_url_11 + ) VALUES ( + :title, :desc, :stock_qty, :specs_json, :sats_price, :cents_price, :digital, :subscription, + :image_url_0, :image_url_1, :image_url_2, :image_url_3, :image_url_4, :image_url_5, + :image_url_6, :image_url_7, :image_url_8, :image_url_9, :image_url_10, :image_url_11 + )"); + + $stmt->execute([ + ':title' => $title, + ':desc' => $desc, + ':stock_qty' => $stock_qty, + ':specs_json' => $specs_json, + ':sats_price' => $sats_price, + ':cents_price' => $cents_price, + ':digital' => (int) $digital, + ':subscription' => (int) $subscription, + ':image_url_0' => $images[0] ?? null, + ':image_url_1' => $images[1] ?? null, + ':image_url_2' => $images[2] ?? null, + ':image_url_3' => $images[3] ?? null, + ':image_url_4' => $images[4] ?? null, + ':image_url_5' => $images[5] ?? null, + ':image_url_6' => $images[6] ?? null, + ':image_url_7' => $images[7] ?? null, + ':image_url_8' => $images[8] ?? null, + ':image_url_9' => $images[9] ?? null, + ':image_url_10' => $images[10] ?? null, + ':image_url_11' => $images[11] ?? null + ]); + } } diff --git a/src/models/quote_items.php b/src/models/quote_items.php new file mode 100644 index 0000000..9df3cf3 --- /dev/null +++ b/src/models/quote_items.php @@ -0,0 +1,70 @@ +exec("CREATE TABLE IF NOT EXISTS quote_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + quote_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + quantity INTEGER NOT NULL CHECK(quantity > 0), + price REAL NOT NULL CHECK(price >= 0), + added_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (quote_id) REFERENCES quotes(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) + );"); + } + + public static function addItem(int $quoteId, int $productId, int $quantity, float $price) + { + if ($quantity <= 0) { + throw new \InvalidArgumentException('Quantity must be greater than zero.'); + } + if ($price < 0) { + throw new \InvalidArgumentException('Price must be non-negative.'); + } + + $stmt = app::$db->prepare("INSERT INTO quote_items (quote_id, product_id, quantity, price) + VALUES (:quote_id, :product_id, :quantity, :price)"); + $stmt->execute([ + 'quote_id' => $quoteId, + 'product_id' => $productId, + 'quantity' => $quantity, + 'price' => $price + ]); + } + + public static function updateItem(int $quoteItemId, int $quantity, float $price) + { + if ($quantity <= 0) { + throw new \InvalidArgumentException('Quantity must be greater than zero.'); + } + if ($price < 0) { + throw new \InvalidArgumentException('Price must be non-negative.'); + } + + $stmt = app::$db->prepare("UPDATE quote_items SET quantity = :quantity, price = :price WHERE quote_item_id = :quote_item_id"); + $stmt->execute([ + 'quote_item_id' => $quoteItemId, + 'quantity' => $quantity, + 'price' => $price + ]); + } + + public static function removeItem(int $quoteItemId) + { + $stmt = app::$db->prepare("DELETE FROM quote_items WHERE quote_item_id = :quote_item_id"); + $stmt->execute(['quote_item_id' => $quoteItemId]); + } + + public static function getQuoteItems(int $quoteId): array + { + $stmt = app::$db->prepare("SELECT * FROM quote_items WHERE quote_id = :quote_id"); + $stmt->execute(['quote_id' => $quoteId]); + return $stmt->fetchAll(); + } +} diff --git a/src/models/quotes.php b/src/models/quotes.php new file mode 100644 index 0000000..3d8e286 --- /dev/null +++ b/src/models/quotes.php @@ -0,0 +1,69 @@ +exec("CREATE TABLE IF NOT EXISTS quotes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + status TEXT CHECK(status IN ('" . implode("', '", self::STATUSES) . "')) NOT NULL DEFAULT 'DRAFT', + FOREIGN KEY (user_id) REFERENCES users(id) + );"); + } + + public static function createQuote(int $userId, string $status = 'DRAFT'): int + { + self::validateStatus($status); + + $stmt = app::$db->prepare("INSERT INTO quotes (user_id, status) + VALUES (:user_id, :status)"); + $stmt->execute([ + 'user_id' => $userId, + 'status' => $status + ]); + + return app::$db->lastInsertId(); + } + + public static function updateStatus(int $quoteId, string $status) + { + self::validateStatus($status); + + $stmt = app::$db->prepare("UPDATE quotes SET status = :status WHERE quote_id = :quote_id"); + $stmt->execute([ + 'quote_id' => $quoteId, + 'status' => $status + ]); + } + + public static function getQuote(int $quoteId): array + { + $stmt = app::$db->prepare("SELECT * FROM quotes WHERE quote_id = :quote_id"); + $stmt->execute(['quote_id' => $quoteId]); + return $stmt->fetch() ?: []; + } + + public static function getUserQuotes(int $userId): array + { + $stmt = app::$db->prepare("SELECT * FROM quotes WHERE user_id = :user_id ORDER BY created_at DESC"); + $stmt->execute(['user_id' => $userId]); + return $stmt->fetchAll(); + } + + private static function validateStatus(string $status) + { + if (!in_array($status, self::STATUSES, true)) { + throw new \InvalidArgumentException("Invalid quote status: $status"); + } + } +} diff --git a/src/models/subscriptions.php b/src/models/subscriptions.php new file mode 100644 index 0000000..04eeb66 --- /dev/null +++ b/src/models/subscriptions.php @@ -0,0 +1,101 @@ +exec("CREATE TABLE IF NOT EXISTS subscriptions ( + subscription_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + start_date DATETIME NOT NULL, + renews_at DATETIME NOT NULL, + status TEXT CHECK(status IN ('" . implode("', '", self::STATUS) . "')) NOT NULL DEFAULT 'COMPLETED', + state TEXT CHECK(state IN ('" . implode("', '", self::STATES) . "')) NOT NULL DEFAULT 'TRIAL', + invoice_date DATETIME NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (product_id) REFERENCES products(id) + );"); + } + + public static function createSubscription(int $userId, int $productId, string $state = 'TRIAL', string $status = 'COMPLETED', string $startDate, string $renewAt, string $invoiceDate): int + { + self::validateState($state); + self::validateStatus($status); + + $stmt = app::$db->prepare("INSERT INTO subscriptions (user_id, product_id, state, status, start_date, renews_at, invoice_date) + VALUES (:user_id, :product_id, :state, :status, :start_date, :renews_at, :invoice_date)"); + $stmt->execute([ + 'user_id' => $userId, + 'product_id' => $productId, + 'state' => $state, + 'status' => $status, + 'start_date' => $startDate, + 'renews_at' => $renewAt, + 'invoice_date' => $invoiceDate + ]); + + return app::$db->lastInsertId(); + } + + public static function updateState(int $subscriptionId, string $state) + { + self::validateState($state); + + $stmt = app::$db->prepare("UPDATE subscriptions SET state = :state WHERE subscription_id = :subscription_id"); + $stmt->execute([ + 'subscription_id' => $subscriptionId, + 'state' => $state + ]); + } + + public static function updateStatus(int $subscriptionId, string $status) + { + self::validateStatus($status); + + $stmt = app::$db->prepare("UPDATE subscriptions SET status = :status WHERE subscription_id = :subscription_id"); + $stmt->execute([ + 'subscription_id' => $subscriptionId, + 'status' => $status + ]); + } + + public static function getSubscription(int $subscriptionId): array + { + $stmt = app::$db->prepare("SELECT * FROM subscriptions WHERE subscription_id = :subscription_id"); + $stmt->execute(['subscription_id' => $subscriptionId]); + return $stmt->fetch() ?: []; + } + + public static function getUserSubscriptions(int $userId): array + { + $stmt = app::$db->prepare("SELECT * FROM subscriptions WHERE user_id = :user_id ORDER BY start_date DESC"); + $stmt->execute(['user_id' => $userId]); + return $stmt->fetchAll(); + } + + private static function validateState(string $state) + { + if (!in_array($state, self::STATES, true)) { + throw new \InvalidArgumentException("Invalid subscription state: $state"); + } + } + + private static function validateStatus(string $status) + { + if (!in_array($status, self::STATUS, true)) { + throw new \InvalidArgumentException("Invalid subscription status: $status"); + } + } +} diff --git a/src/models/transactions.php b/src/models/transactions.php index 4bc3450..adc3d3d 100644 --- a/src/models/transactions.php +++ b/src/models/transactions.php @@ -2,53 +2,89 @@ namespace app\models; use app\app; + class transactions { + const TYPES = ['CREDIT', 'REWARD', 'REDEEM', 'REVOKE', 'DEPOSIT']; + public static function init() { - app::$db->exec("CREATE TABLE transactions ( + $typesList = "'" . implode("', '", self::TYPES) . "'"; + app::$db->exec("CREATE TABLE IF NOT EXISTS transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, - type TEXT CHECK(transaction_type IN ('credit', 'spend', 'withdraw')) NOT NULL, - cents REAL DEFAULT 0, - sats REAL DEFAULT 0, + type TEXT CHECK(type IN ($typesList)) NOT NULL, + cents INTEGER DEFAULT 0, + sats INTEGER DEFAULT 0, date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) )"); } - public static function add($user_id, $transaction_type, $cents, $sats_amount) + public static function add($user_id, $transaction_type, $cents, $sats) { - $query = "INSERT INTO transactions ( - user_id, - type, - cents, - sats - ) VALUES ( - :user_id, - :transaction_type, - :cents, - :sats - )"; + if (!in_array($transaction_type, self::TYPES)) { + throw new \Exception("Invalid transaction type."); + } + if ($cents < 0 || $sats < 0) { + throw new \Exception("Amounts must be non-negative integers."); + } + + $currentBalance = self::getUserBalance($user_id); + + if (in_array($transaction_type, ['REDEEM', 'REVOKE']) && ($currentBalance['total_cents'] < $cents || $currentBalance['total_sats'] < $sats)) { + throw new \Exception("Insufficient funds."); + } + + $query = "INSERT INTO transactions (user_id, type, cents, sats) VALUES (:user_id, :transaction_type, :cents, :sats)"; $stmt = app::$db->prepare($query); $stmt->bindParam(':user_id', $user_id); $stmt->bindParam(':transaction_type', $transaction_type); $stmt->bindParam(':cents', $cents); - $stmt->bindParam(':sats', $sats_amount); + $stmt->bindParam(':sats', $sats); $stmt->execute(); + return app::$db->lastInsertId(); } public static function getUserBalance($user_id) { - $query = "SELECT SUM(cents) AS total_cents, - SUM(sats) AS total_sats - FROM transactions - WHERE user_id = :user_id"; + $query = "SELECT COALESCE(SUM(cents), 0) AS total_cents, COALESCE(SUM(sats), 0) AS total_sats FROM transactions WHERE user_id = :user_id"; $stmt = app::$db->prepare($query); $stmt->bindParam(':user_id', $user_id); $stmt->execute(); - $result = $stmt->fetch(); - return $result; + return $stmt->fetch(); + } + + public static function getRecent($n) + { + $query = "SELECT * FROM transactions ORDER BY date DESC LIMIT :n"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':n', $n, \PDO::PARAM_INT); + $stmt->execute(); + return $stmt->fetchAll(); + } + + public static function getWhales($n, $currency) + { + if (!in_array($currency, ['cents', 'sats'])) { + throw new \Exception("Invalid currency type."); + } + $query = "SELECT user_id, COALESCE(SUM($currency), 0) AS total FROM transactions GROUP BY user_id ORDER BY total DESC LIMIT :n"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':n', $n, \PDO::PARAM_INT); + $stmt->execute(); + return $stmt->fetchAll(); + } + + public static function liabilities($currency) + { + if (!in_array($currency, ['cents', 'sats'])) { + throw new \Exception("Invalid currency type."); + } + $query = "SELECT COALESCE(SUM($currency), 0) AS total FROM transactions"; + $stmt = app::$db->prepare($query); + $stmt->execute(); + return $stmt->fetchColumn(); } } diff --git a/src/models/user_addresses.php b/src/models/user_addresses.php index 418f678..4de093d 100644 --- a/src/models/user_addresses.php +++ b/src/models/user_addresses.php @@ -18,19 +18,27 @@ class user_addresses public static function getShippingByUserId($id) { - $addrs = app::$db->query("SELECT a.* FROM users u + $query = "SELECT a.* FROM users u JOIN user_addresses ua ON u.id = ua.user_id JOIN addresses a ON ua.address_id = a.id - WHERE u.id = '$id' AND a.shipping = 1")->fetch(\PDO::FETCH_ASSOC); + WHERE u.id = :id AND a.shipping = 1"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':id', $id); + $stmt->execute(); + $addrs = $stmt->fetch(\PDO::FETCH_ASSOC); return [$addrs]; } public static function getBillingByUserId($id) { - $addrs = app::$db->query("SELECT a.* FROM users u + $query = "SELECT a.* FROM users u JOIN user_addresses ua ON u.id = ua.user_id JOIN addresses a ON ua.address_id = a.id - WHERE u.id = '$id' AND a.billing = 1")->fetch(\PDO::FETCH_ASSOC); + WHERE u.id = :id AND a.billing = 1"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':id', $id); + $stmt->execute(); + $addrs = $stmt->fetch(\PDO::FETCH_ASSOC); return [$addrs]; } diff --git a/src/models/users.php b/src/models/users.php index 4504003..c4c0fe4 100644 --- a/src/models/users.php +++ b/src/models/users.php @@ -2,6 +2,8 @@ namespace app\models; use app\app; +use swentel\nostr\Key\Key; + class users { public static function init() @@ -14,15 +16,44 @@ class users opt_in_promotional BOOLEAN NOT NULL, verified BOOLEAN NOT NULL, dark_theme BOOLEAN NOT NULL, - generated_base58 TEXT UNIQUE, + nsec TEXT, + npub TEXT NOT NULL, attached_lightning_address TEXT, + replace_email_token TEXT, name TEXT, company_name TEXT, company_type TEXT, company_size TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )"); - app::$db->exec('CREATE INDEX IF NOT EXISTS idx_user_email ON users (email)'); + } + + public static function updateReplaceEmailTokenById($user_id, $replace_token) + { + $query = "UPDATE users SET replace_email_token = :replace_token WHERE id = :user_id"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':replace_token', $replace_token); + $stmt->bindParam(':user_id', $user_id); + $stmt->execute(); + } + + public static function updateEmailById($user_id, $email) + { + $query = "UPDATE users SET email = :email WHERE id = :user_id"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':email', $email); + $stmt->bindParam(':user_id', $user_id); + $stmt->execute(); + users::updateReplaceEmailTokenById($user_id, null); + } + + public static function getByReplaceEmailToken($token) + { + $query = "SELECT * FROM users WHERE replace_email_token = :token"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':token', $token); + $stmt->execute(); + return $stmt->fetch(\PDO::FETCH_ASSOC); } public static function updateProfileById($user_id, $post) @@ -44,20 +75,29 @@ class users public static function add($email, $ship_id, $bill_id, $opt_in_promotional, $verified, $dark_theme) { + $key = new Key(); + $private_key = $key->generatePrivateKey(); + $public_key = $key->getPublicKey($private_key); + $npub = $key->convertPublicKeyToBech32($public_key); + $nsec = $key->convertPrivateKeyToBech32($private_key); $query = "INSERT INTO users ( email, shipping_address_id, billing_address_id, opt_in_promotional, verified, - dark_theme + dark_theme, + nsec, + npub ) VALUES ( :email, :shipping_address_id, :billing_address_id, :opt_in_promotional, :verified, - :dark_theme + :dark_theme, + :nsec, + :npub )"; $stmt = app::$db->prepare($query); $stmt->bindParam(':email', $email); @@ -66,18 +106,35 @@ class users $stmt->bindParam(':opt_in_promotional', $opt_in_promotional); $stmt->bindParam(':verified', $verified); $stmt->bindParam(':dark_theme', $dark_theme); + $stmt->bindParam(':nsec', $nsec); + $stmt->bindParam(':npub', $npub); $stmt->execute(); return app::$db->lastInsertId(); - } public static function verify($email) { - app::$db->exec("UPDATE users SET verified = 1 WHERE email = '$email'"); + $query = "UPDATE users SET verified = 1 WHERE email = :email"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':email', $email); + $stmt->execute(); + } + + public static function getById($id) + { + $query = "SELECT * FROM users WHERE id = :id"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':id', $id); + $stmt->execute(); + return $stmt->fetch(\PDO::FETCH_ASSOC); } public static function getByEmail($email) { - return app::$db->query("SELECT * FROM users WHERE email = '$email'")->fetch(\PDO::FETCH_ASSOC); + $query = "SELECT * FROM users WHERE email = :email"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':email', $email); + $stmt->execute(); + return $stmt->fetch(\PDO::FETCH_ASSOC); } } diff --git a/src/style.css b/src/style.css index 7ceb693..b5c61c9 100644 --- a/src/style.css +++ b/src/style.css @@ -1,32 +1,3 @@ @tailwind base; @tailwind components; @tailwind utilities; -/* #Mega Menu Styles - –––––––––––––––––––––––––––––––––––––––––––––––––– */ -.mega-menu { - opacity: 0; - visibility: hidden; - z-index: -900; - left: 0; - top: 38px; - position: absolute; - text-align: left; - width: 100%; - transition: all 0.15s linear 0s; -} -/* #hoverable Class Styles */ -.hoverable { - position: static; -} -.hoverable > a:after { - content: "\25BC"; - font-size: 10px; - padding-left: 6px; - position: relative; - top: -1px; -} -.hoverable:hover .mega-menu { - opacity: 1; - visibility: visible; - z-index: 900; -} diff --git a/src/views/account/billing.twig b/src/views/account/billing.twig index a45938c..75c9c6b 100644 --- a/src/views/account/billing.twig +++ b/src/views/account/billing.twig @@ -17,8 +17,8 @@
{{ default_billing.name }} {{ default_billing.company }} - {{ default_billing.street }} - {{ default_billing.boxapt }} + {{ default_billing.addressLine1 }} + {{ default_billing.addressLine2 }} {{ default_billing.city }}, {{ default_billing.state }} {{ default_billing.zip }} {{ default_billing.phone }}
diff --git a/src/views/account/index.twig b/src/views/account/index.twig index 0758cbb..0c601cb 100644 --- a/src/views/account/index.twig +++ b/src/views/account/index.twig @@ -40,8 +40,8 @@

{{ default_shipping.name }}

{{ default_shipping.company }}

-

{{ default_shipping.street }}

-

{{ default_billing.boxapt }}

+

{{ default_shipping.addressLine1 }}

+

{{ default_billing.addressLine2 }}

{{ default_shipping.city }}, {{ default_shipping.state }}, {{ default_shipping.zip }}

{{ default_shipping.phone }}

@@ -55,8 +55,8 @@

{{ default_billing.name }}

{{ default_billing.company }}

-

{{ default_billing.street }}

-

{{ default_billing.boxapt }}

+

{{ default_billing.addressLine1 }}

+

{{ default_billing.addressLine2 }}

{{ default_billing.city }}, {{ default_billing.state }}, {{ default_billing.zip }}

{{ default_billing.phone }}

diff --git a/src/views/account/login.twig b/src/views/account/login.twig index 1bf280a..3ecca86 100644 --- a/src/views/account/login.twig +++ b/src/views/account/login.twig @@ -5,7 +5,7 @@ {% include 'lib/rule.twig' %} {% include 'lib/alert.twig' %} -
+ {% include 'lib/input.twig' with { type: 'email', name: 'email', diff --git a/src/views/account/shipping.twig b/src/views/account/shipping.twig index 1b1b49f..00ba5cb 100644 --- a/src/views/account/shipping.twig +++ b/src/views/account/shipping.twig @@ -8,8 +8,8 @@
{{ default_shipping.name }} {{ default_shipping.company }} - {{ default_shipping.street }} - {{ default_shipping.boxapt }} + {{ default_shipping.addressLine1 }} + {{ default_shipping.addressLine2 }} {{ default_shipping.city }}, {{ default_shipping.state }} {{ default_shipping.zip }} {{ default_shipping.phone }}
diff --git a/src/views/account/signup.twig b/src/views/account/signup.twig index ff45978..10c6615 100644 --- a/src/views/account/signup.twig +++ b/src/views/account/signup.twig @@ -34,9 +34,9 @@ {% include 'lib/form/address.twig' with { action: 'shipping', name: session.last_post.shipping_name, - street: session.last_post.shipping_street, + addressLine1: session.last_post.shipping_addressLine1, company: session.last_post.shipping_company, - boxapt: session.last_post.shipping_boxapt, + addressLine2: session.last_post.shipping_addressLine2, city: session.last_post.shipping_city, state: session.last_post.shipping_state, zip: session.last_post.shipping_zip, @@ -51,16 +51,17 @@ {% include 'lib/toggle.twig' with { label: 'Same as shipping', - name: 'use_shipping' + name: 'use_shipping', + on: true } %}