This commit is contained in:
count-null 2025-02-15 00:21:37 -05:00
parent 9b15ac9fd3
commit 27df1a73b5
28 changed files with 1695 additions and 247 deletions

View file

@ -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/"

View file

@ -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"
}
}

825
composer.lock generated
View file

@ -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",

View file

@ -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),

View file

@ -1,5 +1,6 @@
<?php
namespace app;
// for email
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
@ -7,6 +8,7 @@ use PHPMailer\PHPMailer\Exception;
class app
{
public static $db;
public static function init_db()
{
try {
@ -16,6 +18,7 @@ class app
die("Database error: " . $e->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);

View file

@ -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'],

View file

@ -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: <a href='$link'>$link</a>";
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: <a href='$link'>$link</a>";
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();
}
}
}

View file

@ -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);

53
src/models/cart_items.php Normal file
View file

@ -0,0 +1,53 @@
<?php
namespace app\models;
use app\app;
class cart_items
{
public static function init()
{
app::$db->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();
}
}

View file

@ -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;
}
}

View file

@ -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: <a href='$link'>$link</a> or enter this code:<br/>$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;
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace app\models;
use app\app;
class order_items
{
public static function init()
{
app::$db->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();
}
}

View file

@ -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");
}
}
}

View file

@ -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
]);
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace app\models;
use app\app;
class quote_items
{
public static function init()
{
app::$db->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();
}
}

69
src/models/quotes.php Normal file
View file

@ -0,0 +1,69 @@
<?php
namespace app\models;
use app\app;
class quotes
{
private const STATUSES = [
'DRAFT', 'PUBLISHED', 'SENT', 'PURCHASED',
'EXPIRED', 'CANCELED'
];
public static function init()
{
app::$db->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");
}
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace app\models;
use app\app;
class Subscriptions
{
const STATES = [
'TRIAL', 'START', 'RENEWAL'
];
const STATUS = [
'COMPLETED', 'CANCELED'
];
public static function init()
{
app::$db->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");
}
}
}

View file

@ -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();
}
}

View file

@ -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];
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -17,8 +17,8 @@
<div class='flex flex-col'>
<span>{{ default_billing.name }}</span>
<span>{{ default_billing.company }}</span>
<span>{{ default_billing.street }}</span>
<span>{{ default_billing.boxapt }}</span>
<span>{{ default_billing.addressLine1 }}</span>
<span>{{ default_billing.addressLine2 }}</span>
<span>{{ default_billing.city }}, {{ default_billing.state }} {{ default_billing.zip }}</span>
<span>{{ default_billing.phone }}</span>
</div>

View file

@ -40,8 +40,8 @@
<div class="flex flex-col gap-1">
<h4 class="font-semibold">{{ default_shipping.name }}</h4>
<h4 class="font-semibold">{{ default_shipping.company }}</h4>
<h4 class="font-semibold">{{ default_shipping.street }}</h4>
<h4 class="font-semibold">{{ default_billing.boxapt }}</h4>
<h4 class="font-semibold">{{ default_shipping.addressLine1 }}</h4>
<h4 class="font-semibold">{{ default_billing.addressLine2 }}</h4>
<h4 class="font-semibold">{{ default_shipping.city }}, {{ default_shipping.state }}, {{ default_shipping.zip }}</h4>
<h4 class="font-semibold">{{ default_shipping.phone }}</h4>
</div>
@ -55,8 +55,8 @@
<div class="flex flex-col gap-1">
<h4 class="font-semibold">{{ default_billing.name }}</h4>
<h4 class="font-semibold">{{ default_billing.company }}</h4>
<h4 class="font-semibold">{{ default_billing.street }}</h4>
<h4 class="font-semibold">{{ default_billing.boxapt }}</h4>
<h4 class="font-semibold">{{ default_billing.addressLine1 }}</h4>
<h4 class="font-semibold">{{ default_billing.addressLine2 }}</h4>
<h4 class="font-semibold">{{ default_billing.city }}, {{ default_billing.state }}, {{ default_billing.zip }}</h4>
<h4 class="font-semibold">{{ default_billing.phone }}</h4>
</div>

View file

@ -5,7 +5,7 @@
{% include 'lib/rule.twig' %}
</div>
{% include 'lib/alert.twig' %}
<form action="/magic-link" method="get" class="flex flex-col gap-4">
<form action="/account/login" method="post" class="flex flex-col gap-4">
{% include 'lib/input.twig' with {
type: 'email',
name: 'email',

View file

@ -8,8 +8,8 @@
<div class='flex flex-col'>
<span>{{ default_shipping.name }}</span>
<span>{{ default_shipping.company }}</span>
<span>{{ default_shipping.street }}</span>
<span>{{ default_shipping.boxapt }}</span>
<span>{{ default_shipping.addressLine1 }}</span>
<span>{{ default_shipping.addressLine2 }}</span>
<span>{{ default_shipping.city }}, {{ default_shipping.state }} {{ default_shipping.zip }}</span>
<span>{{ default_shipping.phone }}</span>
</div>

View file

@ -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 @@
</h4>
{% include 'lib/toggle.twig' with {
label: 'Same as shipping',
name: 'use_shipping'
name: 'use_shipping',
on: true
} %}
</div>
<div id="billing-address" style="display: none;">
{% include 'lib/form/address.twig' with {
action: 'billing',
name: session.last_post.billing_name,
street: session.last_post.billing_street,
addressLine1: session.last_post.billing_addressLine1,
company: session.last_post.billing_company,
boxapt: session.last_post.billing_boxapt,
addressLine2: session.last_post.billing_addressLine2,
city: session.last_post.billing_city,
state: session.last_post.billing_state,
zip: session.last_post.billing_zip,

View file

@ -1,3 +1,34 @@
<style>
/* #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;
}
</style>
<header class="flex flex-col items-center w-full gap-3 mb-8">
<div class="{{ colors.header.banner }} py-1 text-sm flex w-full justify-center">
<div class="w-[97%] lg:w-[90%] xl:w-4/5 flex justify-between">

View file

@ -14,16 +14,16 @@
} %}
{% include 'lib/input.twig' with {
type: 'text',
name: action ~ '_street',
label: 'Street',
value: street
name: action ~ '_addressLine1',
label: 'Address Line 1',
value: addressLine1
} %}
{% include 'lib/input.twig' with {
type: 'text',
name: action ~ '_boxapt',
label: 'PO Box/Apt#',
name: action ~ '_addressLine2',
label: 'Address Line 2',
optional: true,
value: boxapt
value: addressLine2
} %}
<div class="flex gap-4">
{% include 'lib/input.twig' with {