diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..821c19d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.github \ No newline at end of file diff --git a/drop.sql b/drop.sql new file mode 100644 index 0000000..536db1d --- /dev/null +++ b/drop.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS "account" CASCADE; +DROP TABLE IF EXISTS "coin" CASCADE; +DROP TABLE IF EXISTS "price_history" CASCADE; +DROP TABLE IF EXISTS "session" CASCADE; +DROP TABLE IF EXISTS "transaction" CASCADE; +DROP TABLE IF EXISTS "user" CASCADE; +DROP TABLE IF EXISTS "user_portfolio" CASCADE; +DROP TABLE IF EXISTS "verification" CASCADE; diff --git a/website/bun.lock b/website/bun.lock index 88ffffe..54db6a1 100644 --- a/website/bun.lock +++ b/website/bun.lock @@ -4,6 +4,8 @@ "": { "name": "website", "dependencies": { + "@aws-sdk/client-s3": "^3.815.0", + "@aws-sdk/s3-request-presigner": "^3.815.0", "@tailwindcss/postcss": "^4.1.7", "@tailwindcss/typography": "^0.5.16", "@visx/scale": "^3.12.0", @@ -11,6 +13,7 @@ "drizzle-orm": "^0.33.0", "lightweight-charts": "^5.0.7", "lucide-svelte": "^0.511.0", + "mode-watcher": "^1.0.7", "postgres": "^3.4.4", "svelte-lightweight-charts": "^2.2.0", }, @@ -28,6 +31,7 @@ "prettier-plugin-tailwindcss": "^0.6.11", "svelte": "^5.0.0", "svelte-check": "^4.0.0", + "svelte-sonner": "^1.0.2", "tailwind-merge": "^3.0.2", "tailwind-variants": "^0.2.1", "tailwindcss": "^4.1.7", @@ -42,6 +46,86 @@ "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], + + "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.815.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/credential-provider-node": "3.812.0", "@aws-sdk/middleware-bucket-endpoint": "3.808.0", "@aws-sdk/middleware-expect-continue": "3.804.0", "@aws-sdk/middleware-flexible-checksums": "3.815.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-location-constraint": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-sdk-s3": "3.812.0", "@aws-sdk/middleware-ssec": "3.804.0", "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/signature-v4-multi-region": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.812.0", "@aws-sdk/xml-builder": "3.804.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/eventstream-serde-browser": "^4.0.2", "@smithy/eventstream-serde-config-resolver": "^4.1.0", "@smithy/eventstream-serde-node": "^4.0.2", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-blob-browser": "^4.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/hash-stream-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/md5-js": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.3", "tslib": "^2.6.2" } }, "sha512-tpJyXuGYIPHIu8G53jXQw3mN5ZK6LdL+tcEF3kRJuQ377Vbo+BSfqaizt9Qb3JuOGcNwCp83jd2LlmcXypN5fg=="], + + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.812.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.812.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/credential-provider-env": "3.812.0", "@aws-sdk/credential-provider-http": "3.812.0", "@aws-sdk/credential-provider-process": "3.812.0", "@aws-sdk/credential-provider-sso": "3.812.0", "@aws-sdk/credential-provider-web-identity": "3.812.0", "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.812.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.812.0", "@aws-sdk/credential-provider-http": "3.812.0", "@aws-sdk/credential-provider-ini": "3.812.0", "@aws-sdk/credential-provider-process": "3.812.0", "@aws-sdk/credential-provider-sso": "3.812.0", "@aws-sdk/credential-provider-web-identity": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.812.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.812.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/token-providers": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg=="], + + "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.808.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wEPlNcs8dir9lXbuviEGtSzYSxG/NRKQrJk5ybOc7OpPGHovsN+QhDOdY3lcjOFdwMTiMIG9foUkPz3zBpLB1A=="], + + "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-YW1hySBolALMII6C8y7Z0CRG2UX1dGJjLEBNFeefhO/xP7ZuE1dvnmfJGaEuBMnvc3wkRS63VZ3aqX6sevM1CA=="], + + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.815.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-cv/BO7saBbHTrLMUJiClZHM/GB4xDBbJmZ70f9HwcNBP59tBB8TgF/vSyi8SdFM82TvRP+Zzi1AZ8hXcwElaCg=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-bum1hLVBrn2lJCi423Z2fMUYtsbkGI2s4N+2RI2WSjvbaVyMSv/WcejIrjkqiiMR+2Y7m5exgoKeg4/TODLDPQ=="], + + "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-AMtKnllIWKgoo7hiJfphLYotEwTERfjVMO2+cKAncz9w1g+bnYhHxiVhJJoR94y047c06X4PU5MsTxvdQ73Znw=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-w/qLwL3iq0KOPQNat0Kb7sKndl9BtceigINwBU7SpkYWX9L/Lem6f8NPEKrC9Tl4wDBht3Yztub4oRTy/horJA=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw=="], + + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-e8AqRRIaTsunL1hqtO1hksa9oTYdsIbfezHUyVpPGugUIB1lMqPt/DlBsanI85OzUD711UfNSEcZ1mqAxpDOoA=="], + + "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Tk8jK0gOIUBvEPTz/wwSlP1V70zVQ3QYqsLPAjQRMO6zfOK9ax31dln3MgKvFDJxBydS2tS3wsn53v+brxDxTA=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.812.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.808.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.2", "tslib": "^2.6.2" } }, "sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A=="], + + "@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.815.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-format-url": "3.804.0", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-3xpBwIVPp1xkPXpfGJ2RsgZ+otcwAfAgo5J4buqXkSfBxtJE/dPcaDQEF3AF8APrG/mbuMjkTKIVJ9hNZ34XeA=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.812.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.812.0", "", { "dependencies": { "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.804.0", "", { "dependencies": { "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-A9qnsy9zQ8G89vrPPlNG9d1d8QcKRGqJKqwyGgS0dclJpwy6d1EWgQLIolKPl6vcFpLoe6avLOLxr+h8ur5wpg=="], + + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.804.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.808.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/types": "^4.2.0", "@smithy/util-endpoints": "^3.0.4", "tslib": "^2.6.2" } }, "sha512-N6Lic98uc4ADB7fLWlzx+1uVnq04VgVjngZvwHoujcRg9YDhIg9dUDiTzD5VZv13g1BrPYmvYP1HhsildpGV6w=="], + + "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/querystring-builder": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-1nOwSg7B0bj5LFGor0udF/HSdvDuSCxP+NC0IuSOJ5RgJ2AphFo03pLtK2UwArHY5WWZaejAEz5VBND6xxOEhA=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.804.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/types": "^4.2.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.812.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.804.0", "", { "dependencies": { "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JbGWp36IG9dgxtvC6+YXwt5WDZYfuamWFtVfK6fQpnmL96dx+GUPOXPKRWdw67WLKf2comHY28iX2d3z35I53Q=="], + "@better-auth/utils": ["@better-auth/utils@0.2.5", "", { "dependencies": { "typescript": "^5.8.2", "uncrypto": "^0.1.3" } }, "sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ=="], "@better-fetch/fetch": ["@better-fetch/fetch@1.1.18", "", {}, "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA=="], @@ -182,6 +266,106 @@ "@simplewebauthn/server": ["@simplewebauthn/server@13.1.1", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.3.10", "@peculiar/asn1-ecc": "^2.3.8", "@peculiar/asn1-rsa": "^2.3.8", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8" } }, "sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA=="], + "@smithy/abort-controller": ["@smithy/abort-controller@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-AqXFf6DXnuRBXy4SoK/n1mfgHaKaq36bmkphmD1KO0nHq6xK/g9KHSW4HEsPQUBCGdIEfuJifGHwxFXPIFay9Q=="], + + "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw=="], + + "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.0.0", "", { "dependencies": { "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig=="], + + "@smithy/config-resolver": ["@smithy/config-resolver@4.1.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.2", "@smithy/types": "^4.3.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.3", "tslib": "^2.6.2" } }, "sha512-N5e7ofiyYDmHxnPnqF8L4KtsbSDwyxFRfDK9bp1d9OyPO4ytRLd0/XxCqi5xVaaqB65v4woW8uey6jND6zxzxQ=="], + + "@smithy/core": ["@smithy/core@3.4.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.6", "@smithy/protocol-http": "^5.1.1", "@smithy/types": "^4.3.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.3", "@smithy/util-stream": "^4.2.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-dDYISQo7k0Ml/rXlFIjkTmTcQze/LxhtIRAEmZ6HJ/EI0inVxVEVnrUXJ7jPx6ZP0GHUhFm40iQcCgS5apXIXA=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.2", "@smithy/property-provider": "^4.0.3", "@smithy/types": "^4.3.0", "@smithy/url-parser": "^4.0.3", "tslib": "^2.6.2" } }, "sha512-saEAGwrIlkb9XxX/m5S5hOtzjoJPEK6Qw2f9pYTbIsMPOFyGSXBBTw95WbOyru8A1vIS2jVCCU1Qhz50QWG3IA=="], + + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.0.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.3.0", "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-V22KIPXZsE2mc4zEgYGANM/7UbL9jWlOACEolyGyMuTY+jjHJ2PQ0FdopOTS1CS7u6PlAkALmypkv2oQ4aftcg=="], + + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.0.3", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.3", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-oe1d/tfCGVZBMX8O6HApaM4G+fF9JNdyLP7tWXt00epuL/kLOdp/4o9VqheLFeJaXgao+9IaBgs/q/oM48hxzg=="], + + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.1.1", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-XXCPGjRNwpFWHKQJMKIjGLfFKYULYckFnxGcWmBC2mBf3NsrvUKgqHax4NCqc0TfbDAimPDHOc6HOKtzsXK9Gw=="], + + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.0.3", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.3", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-HOEbRmm9TrikCoFrypYu0J/gC4Lsk8gl5LtOz1G3laD2Jy44+ht2Pd2E9qjNQfhMJIzKDZ/gbuUH0s0v4kWQ0A=="], + + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.0.3", "", { "dependencies": { "@smithy/eventstream-codec": "^4.0.3", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-ShOP512CZrYI9n+h64PJ84udzoNHUQtPddyh1j175KNTKsSnMEDNscOWJWyEoLQiuhWWw51lSa+k6ea9ZGXcRg=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.3", "", { "dependencies": { "@smithy/protocol-http": "^5.1.1", "@smithy/querystring-builder": "^4.0.3", "@smithy/types": "^4.3.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-yBZwavI31roqTndNI7ONHqesfH01JmjJK6L3uUpZAhyAmr86LN5QiPzfyZGIxQmed8VEK2NRSQT3/JX5V1njfQ=="], + + "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.0.3", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.0.0", "@smithy/chunked-blob-reader-native": "^4.0.0", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-37wZYU/XI2cOF4hgNDNMzZNAuNtJTkZFWxcpagQrnf6PYU/6sJ6y5Ey9Bp4vzi9nteex/ImxAugfsF3XGLrqWA=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-W5Uhy6v/aYrgtjh9y0YP332gIQcwccQ+EcfWhllL0B9rPae42JngTTUpb8W6wuxaNFzqps4xq5klHckSSOy5fw=="], + + "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-CAwAvztwGYHHZGGcXtbinNxytaj5FNZChz8V+o7eNUAi5BgVqnF91Z3cJSmaE9O7FYUQVrIzGAB25Aok9T5KHQ=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-1Bo8Ur1ZGqxvwTqBmv6DZEn0rXtwJGeqiiO2/JFcCtz3nBakOqeXbJBElXJMMzd0ghe8+eB6Dkw98nMYctgizg=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], + + "@smithy/md5-js": ["@smithy/md5-js@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-m95Z+1UJFPq4cv/R6TPMLYkoau7cNJYA5GLuuUJjfmF+Zrad4yaupIWeGGzIinf8pD1L+CIAxjh8eowPvyL7Dw=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.0.3", "", { "dependencies": { "@smithy/protocol-http": "^5.1.1", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-NE/Zph4BP5u16bzYq2csq9qD0T6UBLeg4AuNrwNJ7Gv9uLYaGEgelZUOdRndGdMGcUfSGvNlXGb2aA2hPCwJ6g=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.1.7", "", { "dependencies": { "@smithy/core": "^3.4.0", "@smithy/middleware-serde": "^4.0.6", "@smithy/node-config-provider": "^4.1.2", "@smithy/shared-ini-file-loader": "^4.0.3", "@smithy/types": "^4.3.0", "@smithy/url-parser": "^4.0.3", "@smithy/util-middleware": "^4.0.3", "tslib": "^2.6.2" } }, "sha512-KDzM7Iajo6K7eIWNNtukykRT4eWwlHjCEsULZUaSfi/SRSBK8BPRqG5FsVfp58lUxcvre8GT8AIPIqndA0ERKw=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.1.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.2", "@smithy/protocol-http": "^5.1.1", "@smithy/service-error-classification": "^4.0.4", "@smithy/smithy-client": "^4.3.0", "@smithy/types": "^4.3.0", "@smithy/util-middleware": "^4.0.3", "@smithy/util-retry": "^4.0.4", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-e2OtQgFzzlSG0uCjcJmi02QuFSRTrpT11Eh2EcqqDFy7DYriteHZJkkf+4AsxsrGDugAtPFcWBz1aq06sSX5fQ=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.0.6", "", { "dependencies": { "@smithy/protocol-http": "^5.1.1", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-YECyl7uNII+jCr/9qEmCu8xYL79cU0fqjo0qxpcVIU18dAPHam/iYwcknAu4Jiyw1uN+sAx7/SMf/Kmef/Jjsg=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-baeV7t4jQfQtFxBADFmnhmqBmqR38dNU5cvEgHcMK/Kp3D3bEI0CouoX2Sr/rGuntR+Eg0IjXdxnGGTc6SbIkw=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.1.2", "", { "dependencies": { "@smithy/property-provider": "^4.0.3", "@smithy/shared-ini-file-loader": "^4.0.3", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-SUvNup8iU1v7fmM8XPk+27m36udmGCfSz+VZP5Gb0aJ3Ne0X28K/25gnsrg3X1rWlhcnhzNUUysKW/Ied46ivQ=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.5", "", { "dependencies": { "@smithy/abort-controller": "^4.0.3", "@smithy/protocol-http": "^5.1.1", "@smithy/querystring-builder": "^4.0.3", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-T7QglZC1vS7SPT44/1qSIAQEx5bFKb3LfO6zw/o4Xzt1eC5HNoH1TkS4lMYA9cWFbacUhx4hRl/blLun4EOCkg=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-Wcn17QNdawJZcZZPBuMuzyBENVi1AXl4TdE0jvzo4vWX2x5df/oMlmr/9M5XAAC6+yae4kWZlOYIsNsgDrMU9A=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.1.1", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-Vsay2mzq05DwNi9jK01yCFtfvu9HimmgC7a4HTs7lhX12Sx8aWsH0mfz6q/02yspSp+lOB+Q2HJwi4IV2GKz7A=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-UUzIWMVfPmDZcOutk2/r1vURZqavvQW0OHvgsyNV0cKupChvqg+/NKPRMaMEe+i8tP96IthMFeZOZWpV+E4RAw=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-K5M4ZJQpFCblOJ5Oyw7diICpFg1qhhR47m2/5Ef1PhGE19RaIZf50tjYFrxa6usqcuXyTiFPGo4d1geZdH4YcQ=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.0" } }, "sha512-W5ScbQ1bTzgH91kNEE2CvOzM4gXlDOqdow4m8vMFSIXCel2scbHwjflpVNnC60Y3F1m5i7w2gQg9lSnR+JsJAA=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-vHwlrqhZGIoLwaH8vvIjpHnloShqdJ7SUPNM2EQtEox+yEDFTVQ7E+DLZ+6OhnYEgFUwPByJyz6UZaOu2tny6A=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.1.1", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "@smithy/protocol-http": "^5.1.1", "@smithy/types": "^4.3.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-middleware": "^4.0.3", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-zy8Repr5zvT0ja+Tf5wjV/Ba6vRrhdiDcp/ww6cvqYbSEudIkziDe3uppNRlFoCViyJXdPnLcwyZdDLA4CHzSg=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.3.0", "", { "dependencies": { "@smithy/core": "^3.4.0", "@smithy/middleware-endpoint": "^4.1.7", "@smithy/middleware-stack": "^4.0.3", "@smithy/protocol-http": "^5.1.1", "@smithy/types": "^4.3.0", "@smithy/util-stream": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-DNsRA38pN6tYHUjebmwD9e4KcgqTLldYQb2gC6K+oxXYdCTxPn6wV9+FvOa6wrU2FQEnGJoi+3GULzOTKck/tg=="], + + "@smithy/types": ["@smithy/types@4.3.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+1iaIQHthDh9yaLhRzaoQxRk+l9xlk+JjMFxGRhNLz+m9vKOkjNeU8QuB4w3xvzHyVR/BVlp/4AXDHjoRIkfgQ=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.0.3", "", { "dependencies": { "@smithy/querystring-parser": "^4.0.3", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-n5/DnosDu/tweOqUUNtUbu7eRIR4J/Wz9nL7V5kFYQQVb8VYdj7a4G5NJHCw6o21ul7CvZoJkOpdTnsQDLT0tQ=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.0.15", "", { "dependencies": { "@smithy/property-provider": "^4.0.3", "@smithy/smithy-client": "^4.3.0", "@smithy/types": "^4.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-bJJ/B8owQbHAflatSq92f9OcV8858DJBQF1Y3GRjB8psLyUjbISywszYPFw16beREHO/C3I3taW4VGH+tOuwrQ=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.0.15", "", { "dependencies": { "@smithy/config-resolver": "^4.1.3", "@smithy/credential-provider-imds": "^4.0.5", "@smithy/node-config-provider": "^4.1.2", "@smithy/property-provider": "^4.0.3", "@smithy/smithy-client": "^4.3.0", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-8CUrEW2Ni5q+NmYkj8wsgkfqoP7l4ZquptFbq92yQE66xevc4SxqP2zH6tMtN158kgBqBDsZ+qlrRwXWOjCR8A=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.0.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.2", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-PjDpqLk24/vAl340tmtCA++Q01GRRNH9cwL9qh46NspAX9S+IQVcK+GOzPt0GLJ6KYGyn8uOgo2kvJhiThclJw=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.0.3", "", { "dependencies": { "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-iIsC6qZXxkD7V3BzTw3b1uK8RVC1M8WvwNxK1PKrH9FnxntCd30CSunXjL/8iJBE8Z0J14r2P69njwIpRG4FBQ=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.0.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.0.4", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-Aoqr9W2jDYGrI6OxljN8VmLDQIGO4VdMAUKMf9RGqLG8hn6or+K41NEy1Y5dtum9q8F7e0obYAuKl2mt/GnpZg=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.2.1", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.3", "@smithy/node-http-handler": "^4.0.5", "@smithy/types": "^4.3.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-W3IR0x5DY6iVtjj5p902oNhD+Bz7vs5S+p6tppbPa509rV9BdeXZjGuRSCtVEad9FA0Mba+tNUtUmtnSI1nwUw=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="], + + "@smithy/util-waiter": ["@smithy/util-waiter@4.0.4", "", { "dependencies": { "@smithy/abort-controller": "^4.0.3", "@smithy/types": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-73aeIvHjtSB6fd9I08iFaQIGTICKpLrI3EtlWAkStVENGo1ARMq9qdoD4QwkY0RUp6A409xlgbD9NCCfCF5ieg=="], + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], "@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@3.3.1", "", { "dependencies": { "import-meta-resolve": "^4.1.0" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ=="], @@ -270,6 +454,8 @@ "bits-ui": ["bits-ui@1.5.3", "", { "dependencies": { "@floating-ui/core": "^1.6.4", "@floating-ui/dom": "^1.6.7", "@internationalized/date": "^3.5.6", "esm-env": "^1.1.2", "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1", "tabbable": "^6.2.0" }, "peerDependencies": { "svelte": "^5.11.0" } }, "sha512-BTZ9/GU11DaEGyQp+AY+sXCMLZO0gbDC5J8l7+Ngj4Vf6hNOwrpMmoh5iuKktA6cphXYolVkUDgBWmkh415I+w=="], + "bowser": ["bowser@2.11.0", "", {}, "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="], + "browserslist": ["browserslist@4.24.5", "", { "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -336,6 +522,8 @@ "fancy-canvas": ["fancy-canvas@2.1.0", "", {}, "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ=="], + "fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="], + "fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], "fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], @@ -404,6 +592,8 @@ "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + "mode-watcher": ["mode-watcher@1.0.7", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-ZGA7ZGdOvBJeTQkzdBOnXSgTkO6U6iIFWJoyGCTt6oHNg9XP9NBvS26De+V4W2aqI+B0yYXUskFG2VnEo3zyMQ=="], + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -464,6 +654,8 @@ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], + "style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="], "svelte": ["svelte@5.32.0", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2WXcm+mx4D99pb5gvGYiEC6EkPOfzCcgXxcVrxMkAythwzYH5Frr29i3C431U4B8LxXRh9WnFPAz+OzIcFdM7g=="], @@ -472,6 +664,8 @@ "svelte-lightweight-charts": ["svelte-lightweight-charts@2.2.0", "", { "peerDependencies": { "lightweight-charts": ">=4.0.0", "svelte": ">=3.44.0" } }, "sha512-LXdha4vfLMuOPc0Yyetu+DLSDJkPryGkufUQgpCkfguCscSQUcrLiI9MdqKwQk6Fkm6AZbg8SQ5qDIYxcyC+Dg=="], + "svelte-sonner": ["svelte-sonner@1.0.2", "", { "dependencies": { "runed": "^0.26.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-hoidgv7hrk3XNZzjXj6/frsvZOiUOtf7Tn2du/hTu1G9PchJGEoy4EpIKyfhVKBiBrxaNFaYPFxN4pHLHCoe/w=="], + "svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], @@ -500,6 +694,8 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + "vite": ["vite@5.4.19", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA=="], "vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="], @@ -510,6 +706,12 @@ "zod": ["zod@3.25.17", "", {}, "sha512-8hQzQ/kMOIFbwOgPrm9Sf9rtFHpFUMy4HvN0yEB0spw14aYi0uT5xG5CE2DB9cd51GWNsz+DNO7se1kztHMKnw=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], @@ -530,10 +732,20 @@ "d3-time/d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + "mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="], + + "svelte-sonner/runed": ["runed@0.26.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-qWFv0cvLVRd8pdl/AslqzvtQyEn5KaIugEernwg9G98uJVSZcs/ygvPBvF80LA46V8pwRvSKnaVLDI3+i2wubw=="], + "tailwind-variants/tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="], "vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -623,5 +835,11 @@ "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], } } diff --git a/website/drizzle/0000_romantic_firebrand.sql b/website/drizzle/0000_giant_vanisher.sql similarity index 94% rename from website/drizzle/0000_romantic_firebrand.sql rename to website/drizzle/0000_giant_vanisher.sql index 263de42..9fb217e 100644 --- a/website/drizzle/0000_romantic_firebrand.sql +++ b/website/drizzle/0000_giant_vanisher.sql @@ -81,7 +81,10 @@ CREATE TABLE IF NOT EXISTS "user" ( "is_banned" boolean DEFAULT false, "ban_reason" text, "base_currency_balance" numeric(19, 4) DEFAULT '10000.0000' NOT NULL, - CONSTRAINT "user_email_unique" UNIQUE("email") + "bio" varchar(160) DEFAULT 'Hello am 48 year old man from somalia. Sorry for my bed england. I selled my wife for internet connection for play “conter stirk”', + "username" varchar(30) NOT NULL, + CONSTRAINT "user_email_unique" UNIQUE("email"), + CONSTRAINT "user_username_unique" UNIQUE("username") ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "user_portfolio" ( diff --git a/website/drizzle/0001_last_selene.sql b/website/drizzle/0001_last_selene.sql new file mode 100644 index 0000000..51b8e1e --- /dev/null +++ b/website/drizzle/0001_last_selene.sql @@ -0,0 +1 @@ +ALTER TABLE "coin" ADD COLUMN "icon" text; \ No newline at end of file diff --git a/website/drizzle/meta/0000_snapshot.json b/website/drizzle/meta/0000_snapshot.json index bea5f71..fdaa1cd 100644 --- a/website/drizzle/meta/0000_snapshot.json +++ b/website/drizzle/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "d8f103f7-02e7-4506-95a5-4993abe53030", + "id": "75ce764f-a039-4e66-9ebb-620e13d1fa85", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -530,6 +530,19 @@ "primaryKey": false, "notNull": true, "default": "'10000.0000'" + }, + "bio": { + "name": "bio", + "type": "varchar(160)", + "primaryKey": false, + "notNull": false, + "default": "'Hello am 48 year old man from somalia. Sorry for my bed england. I selled my wife for internet connection for play “conter stirk”'" + }, + "username": { + "name": "username", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true } }, "indexes": {}, @@ -542,6 +555,13 @@ "columns": [ "email" ] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] } } }, diff --git a/website/drizzle/meta/0001_snapshot.json b/website/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..4db6461 --- /dev/null +++ b/website/drizzle/meta/0001_snapshot.json @@ -0,0 +1,709 @@ +{ + "id": "a272e8ea-cd8d-4f01-b826-5c4ea55499c4", + "prevId": "75ce764f-a039-4e66-9ebb-620e13d1fa85", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.coin": { + "name": "coin", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "symbol": { + "name": "symbol", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "initial_supply": { + "name": "initial_supply", + "type": "numeric(28, 8)", + "primaryKey": false, + "notNull": true + }, + "circulating_supply": { + "name": "circulating_supply", + "type": "numeric(28, 8)", + "primaryKey": false, + "notNull": true + }, + "current_price": { + "name": "current_price", + "type": "numeric(19, 8)", + "primaryKey": false, + "notNull": true + }, + "market_cap": { + "name": "market_cap", + "type": "numeric(28, 4)", + "primaryKey": false, + "notNull": true + }, + "volume_24h": { + "name": "volume_24h", + "type": "numeric(28, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.0000'" + }, + "change_24h": { + "name": "change_24h", + "type": "numeric(8, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.0000'" + }, + "pool_coin_amount": { + "name": "pool_coin_amount", + "type": "numeric(28, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "pool_base_currency_amount": { + "name": "pool_base_currency_amount", + "type": "numeric(28, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0.0000'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_listed": { + "name": "is_listed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "coin_creator_id_user_id_fk": { + "name": "coin_creator_id_user_id_fk", + "tableFrom": "coin", + "tableTo": "user", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "coin_symbol_unique": { + "name": "coin_symbol_unique", + "nullsNotDistinct": false, + "columns": [ + "symbol" + ] + } + } + }, + "public.price_history": { + "name": "price_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "coin_id": { + "name": "coin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "numeric(19, 8)", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "price_history_coin_id_coin_id_fk": { + "name": "price_history_coin_id_coin_id_fk", + "tableFrom": "price_history", + "tableTo": "coin", + "columnsFrom": [ + "coin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + } + }, + "public.transaction": { + "name": "transaction", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "coin_id": { + "name": "coin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "transaction_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(28, 8)", + "primaryKey": false, + "notNull": true + }, + "price_per_coin": { + "name": "price_per_coin", + "type": "numeric(19, 8)", + "primaryKey": false, + "notNull": true + }, + "total_base_currency_amount": { + "name": "total_base_currency_amount", + "type": "numeric(28, 4)", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "transaction_user_id_user_id_fk": { + "name": "transaction_user_id_user_id_fk", + "tableFrom": "transaction", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transaction_coin_id_coin_id_fk": { + "name": "transaction_coin_id_coin_id_fk", + "tableFrom": "transaction", + "tableTo": "coin", + "columnsFrom": [ + "coin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_banned": { + "name": "is_banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_currency_balance": { + "name": "base_currency_balance", + "type": "numeric(19, 4)", + "primaryKey": false, + "notNull": true, + "default": "'10000.0000'" + }, + "bio": { + "name": "bio", + "type": "varchar(160)", + "primaryKey": false, + "notNull": false, + "default": "'Hello am 48 year old man from somalia. Sorry for my bed england. I selled my wife for internet connection for play “conter stirk”'" + }, + "username": { + "name": "username", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.user_portfolio": { + "name": "user_portfolio", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "coin_id": { + "name": "coin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(28, 8)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_portfolio_user_id_user_id_fk": { + "name": "user_portfolio_user_id_user_id_fk", + "tableFrom": "user_portfolio", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_portfolio_coin_id_coin_id_fk": { + "name": "user_portfolio_coin_id_coin_id_fk", + "tableFrom": "user_portfolio", + "tableTo": "coin", + "columnsFrom": [ + "coin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_portfolio_user_id_coin_id_pk": { + "name": "user_portfolio_user_id_coin_id_pk", + "columns": [ + "user_id", + "coin_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.transaction_type": { + "name": "transaction_type", + "schema": "public", + "values": [ + "BUY", + "SELL" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/website/drizzle/meta/_journal.json b/website/drizzle/meta/_journal.json index 45ee77f..4833492 100644 --- a/website/drizzle/meta/_journal.json +++ b/website/drizzle/meta/_journal.json @@ -5,8 +5,15 @@ { "idx": 0, "version": "7", - "when": 1747913743324, - "tag": "0000_romantic_firebrand", + "when": 1747924055062, + "tag": "0000_giant_vanisher", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1747991689472, + "tag": "0001_last_selene", "breakpoints": true } ] diff --git a/website/package.json b/website/package.json index a184d1a..d81d09f 100644 --- a/website/package.json +++ b/website/package.json @@ -29,6 +29,7 @@ "prettier-plugin-tailwindcss": "^0.6.11", "svelte": "^5.0.0", "svelte-check": "^4.0.0", + "svelte-sonner": "^1.0.2", "tailwind-merge": "^3.0.2", "tailwind-variants": "^0.2.1", "tailwindcss": "^4.1.7", @@ -37,6 +38,8 @@ "vite": "^5.4.11" }, "dependencies": { + "@aws-sdk/client-s3": "^3.815.0", + "@aws-sdk/s3-request-presigner": "^3.815.0", "@tailwindcss/postcss": "^4.1.7", "@tailwindcss/typography": "^0.5.16", "@visx/scale": "^3.12.0", diff --git a/website/src/app.css b/website/src/app.css index 2c1424f..a861293 100644 --- a/website/src/app.css +++ b/website/src/app.css @@ -2,12 +2,15 @@ @import "tw-animate-css"; +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'); + @custom-variant dark (&:is(.dark *)); -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'); :root { --radius: 0.5rem; + --success: oklch(0.637 0.237 205.331); + --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); @@ -96,6 +99,7 @@ --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-border: var(--border); + --color-success: var(--success); --color-input: var(--input); --color-ring: var(--ring); --color-chart-1: var(--chart-1); diff --git a/website/src/lib/auth.ts b/website/src/lib/auth.ts index abe3d39..39527a0 100644 --- a/website/src/lib/auth.ts +++ b/website/src/lib/auth.ts @@ -1,7 +1,11 @@ +// src/lib/auth.ts (or your auth config file) import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { env } from '$env/dynamic/private'; import { db } from "./server/db"; +import * as schema from "./server/db/schema"; +import { generateUsername } from "./utils/random"; +import { uploadProfilePicture } from "./server/s3"; if (!env.GOOGLE_CLIENT_ID) throw new Error('GOOGLE_CLIENT_ID is not set'); if (!env.GOOGLE_CLIENT_SECRET) throw new Error('GOOGLE_CLIENT_SECRET is not set'); @@ -13,44 +17,67 @@ export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", + schema: schema, }), socialProviders: { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, - } - }, + mapProfileToUser: async (profile) => { + const newUsername = generateUsername(); + let s3ImageKey: string | null = null; - session: { - cookieCache: { - enabled: true, - maxAge: 60 * 5, // 5 minutes + if (profile.picture) { + try { + const response = await fetch(profile.picture); + if (!response.ok) { + console.error(`Failed to fetch profile picture: ${response.statusText}`); + } else { + const blob = await response.blob(); + const arrayBuffer = await blob.arrayBuffer(); + s3ImageKey = await uploadProfilePicture( + profile.sub, // Using Google 'sub' for a unique identifier + new Uint8Array(arrayBuffer), + blob.type, + blob.size + ); + } + } catch (error) { + console.error('Failed to upload profile picture during social login:', error); + } + } + + return { + name: profile.name, + email: profile.email, + image: s3ImageKey, // Store S3 key in the standard 'image' field + username: newUsername, + }; + }, } }, user: { additionalFields: { - isAdmin: { - type: "boolean", - required: true, - defaultValue: false, - input: false - }, - isBanned: { - type: "boolean", - required: true, - defaultValue: false, - input: false - }, - banReason: { - type: "string", - required: false, - defaultValue: null, - input: false - } - }, - deleteUser: { enabled: true } + username: { type: "string", required: true, input: false }, + isAdmin: { type: "boolean", required: false, input: false }, + isBanned: { type: "boolean", required: false, input: false }, + banReason: { type: "string", required: false, input: false }, + baseCurrencyBalance: { type: "string", required: false, input: false }, + bio: { type: "string", required: false }, + // Ensure 'image' is not listed here if it's a core field, + // or ensure 'avatarUrl' is used consistently if it is an additional field. + // Based on current setup, 'image' is core. + } + }, + session: { + cookieCache: { + enabled: true, + maxAge: 60 * 5, + } }, advanced: { - generateId: false, + database: { + generateId: false, + } } }); \ No newline at end of file diff --git a/website/src/lib/components/self/AppSidebar.svelte b/website/src/lib/components/self/AppSidebar.svelte index da06d5a..312d82a 100644 --- a/website/src/lib/components/self/AppSidebar.svelte +++ b/website/src/lib/components/self/AppSidebar.svelte @@ -2,6 +2,7 @@ import * as Sidebar from '$lib/components/ui/sidebar'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu'; import * as Avatar from '$lib/components/ui/avatar'; + import { Badge } from '$lib/components/ui/badge'; import { Moon, Sun, @@ -15,15 +16,19 @@ BadgeCheckIcon, CreditCardIcon, BellIcon, - LogOutIcon + LogOutIcon, + Wallet } from 'lucide-svelte'; import { mode, setMode } from 'mode-watcher'; import type { HTMLAttributes } from 'svelte/elements'; import { USER_DATA } from '$lib/stores/user-data'; + import { PORTFOLIO_DATA, fetchPortfolioData } from '$lib/stores/portfolio-data'; import { useSidebar } from '$lib/components/ui/sidebar/index.js'; import SignInConfirmDialog from './SignInConfirmDialog.svelte'; import { signOut } from '$lib/auth-client'; + import { getPublicUrl } from '$lib/utils'; + import { goto } from '$app/navigation'; const data = { navMain: [ @@ -39,6 +44,15 @@ const { setOpenMobile, isMobile } = useSidebar(); let shouldSignIn = $state(false); + // Fetch portfolio data when user is authenticated + $effect(() => { + if ($USER_DATA) { + fetchPortfolioData(); + } else { + PORTFOLIO_DATA.set(null); + } + }); + function handleNavClick(title: string) { setOpenMobile(false); } @@ -47,6 +61,13 @@ setMode(mode.current === 'light' ? 'dark' : 'light'); setOpenMobile(false); } + + function formatCurrency(value: number): string { + return value.toLocaleString('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); + } @@ -121,6 +142,36 @@ + + + {#if $USER_DATA && $PORTFOLIO_DATA} + + Portfolio + +
+
+
+ + Total Value +
+ + ${formatCurrency($PORTFOLIO_DATA.totalValue)} + +
+
+
+ Cash: + ${formatCurrency($PORTFOLIO_DATA.baseCurrencyBalance)} +
+
+ Coins: + ${formatCurrency($PORTFOLIO_DATA.totalCoinValue)} +
+
+
+
+
+ {/if} {#if $USER_DATA} @@ -136,13 +187,12 @@ {...props} > - - CN + + ?
{$USER_DATA.name} - $35,674.34 - + @{$USER_DATA.username}
@@ -157,12 +207,12 @@
- - CN + + ?
{$USER_DATA.name} - {$USER_DATA.email} + @{$USER_DATA.username}
@@ -175,7 +225,7 @@ - + goto('/settings')}> Account diff --git a/website/src/lib/components/ui/alert/alert-description.svelte b/website/src/lib/components/ui/alert/alert-description.svelte new file mode 100644 index 0000000..8b56aed --- /dev/null +++ b/website/src/lib/components/ui/alert/alert-description.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/website/src/lib/components/ui/alert/alert-title.svelte b/website/src/lib/components/ui/alert/alert-title.svelte new file mode 100644 index 0000000..77e45ad --- /dev/null +++ b/website/src/lib/components/ui/alert/alert-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/website/src/lib/components/ui/alert/alert.svelte b/website/src/lib/components/ui/alert/alert.svelte new file mode 100644 index 0000000..2b2eff9 --- /dev/null +++ b/website/src/lib/components/ui/alert/alert.svelte @@ -0,0 +1,44 @@ + + + + + diff --git a/website/src/lib/components/ui/alert/index.ts b/website/src/lib/components/ui/alert/index.ts new file mode 100644 index 0000000..97e21b4 --- /dev/null +++ b/website/src/lib/components/ui/alert/index.ts @@ -0,0 +1,14 @@ +import Root from "./alert.svelte"; +import Description from "./alert-description.svelte"; +import Title from "./alert-title.svelte"; +export { alertVariants, type AlertVariant } from "./alert.svelte"; + +export { + Root, + Description, + Title, + // + Root as Alert, + Description as AlertDescription, + Title as AlertTitle, +}; diff --git a/website/src/lib/components/ui/hover-card/hover-card-content.svelte b/website/src/lib/components/ui/hover-card/hover-card-content.svelte new file mode 100644 index 0000000..e1d592a --- /dev/null +++ b/website/src/lib/components/ui/hover-card/hover-card-content.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/website/src/lib/components/ui/hover-card/hover-card-trigger.svelte b/website/src/lib/components/ui/hover-card/hover-card-trigger.svelte new file mode 100644 index 0000000..322172b --- /dev/null +++ b/website/src/lib/components/ui/hover-card/hover-card-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/website/src/lib/components/ui/hover-card/index.ts b/website/src/lib/components/ui/hover-card/index.ts new file mode 100644 index 0000000..85f3949 --- /dev/null +++ b/website/src/lib/components/ui/hover-card/index.ts @@ -0,0 +1,14 @@ +import { LinkPreview as HoverCardPrimitive } from "bits-ui"; +import Content from "./hover-card-content.svelte"; +import Trigger from "./hover-card-trigger.svelte"; + +const Root = HoverCardPrimitive.Root; + +export { + Root, + Content, + Trigger, + Root as HoverCard, + Content as HoverCardContent, + Trigger as HoverCardTrigger, +}; diff --git a/website/src/lib/components/ui/label/index.ts b/website/src/lib/components/ui/label/index.ts new file mode 100644 index 0000000..8bfca0b --- /dev/null +++ b/website/src/lib/components/ui/label/index.ts @@ -0,0 +1,7 @@ +import Root from "./label.svelte"; + +export { + Root, + // + Root as Label, +}; diff --git a/website/src/lib/components/ui/label/label.svelte b/website/src/lib/components/ui/label/label.svelte new file mode 100644 index 0000000..d0afda3 --- /dev/null +++ b/website/src/lib/components/ui/label/label.svelte @@ -0,0 +1,20 @@ + + + diff --git a/website/src/lib/components/ui/sonner/index.ts b/website/src/lib/components/ui/sonner/index.ts new file mode 100644 index 0000000..1ad9f4a --- /dev/null +++ b/website/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./sonner.svelte"; diff --git a/website/src/lib/components/ui/sonner/sonner.svelte b/website/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 0000000..67669b7 --- /dev/null +++ b/website/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,13 @@ + + + diff --git a/website/src/lib/components/ui/textarea/index.ts b/website/src/lib/components/ui/textarea/index.ts new file mode 100644 index 0000000..ace797a --- /dev/null +++ b/website/src/lib/components/ui/textarea/index.ts @@ -0,0 +1,7 @@ +import Root from "./textarea.svelte"; + +export { + Root, + // + Root as Textarea, +}; diff --git a/website/src/lib/components/ui/textarea/textarea.svelte b/website/src/lib/components/ui/textarea/textarea.svelte new file mode 100644 index 0000000..545b377 --- /dev/null +++ b/website/src/lib/components/ui/textarea/textarea.svelte @@ -0,0 +1,22 @@ + + + diff --git a/website/src/lib/data/coins.ts b/website/src/lib/data/coins.ts deleted file mode 100644 index 6974b06..0000000 --- a/website/src/lib/data/coins.ts +++ /dev/null @@ -1,103 +0,0 @@ -export interface Coin { - id: number; - name: string; - symbol: string; - price: number; - change24h: number; - volume24h: number; - marketCap: number; - priceHistory: { date: string; price: number }[]; -} - -export const coins: Coin[] = [ - { - id: 1, - name: 'Bitcoin', - symbol: 'BTC', - price: 67890.42, - change24h: 2.3, - volume24h: 28500000000, - marketCap: 1320000000000, - priceHistory: [ - { date: '2025-05-14', price: 66250.18 }, - { date: '2025-05-15', price: 65890.34 }, - { date: '2025-05-16', price: 66780.12 }, - { date: '2025-05-17', price: 66920.45 }, - { date: '2025-05-18', price: 67120.78 }, - { date: '2025-05-19', price: 67450.23 }, - { date: '2025-05-20', price: 67890.42 } - ] - }, - { - id: 2, - name: 'Ethereum', - symbol: 'ETH', - price: 3456.78, - change24h: -1.2, - volume24h: 15200000000, - marketCap: 420000000000, - priceHistory: [ - { date: '2025-05-14', price: 3520.45 }, - { date: '2025-05-15', price: 3490.23 }, - { date: '2025-05-16', price: 3475.67 }, - { date: '2025-05-17', price: 3460.12 }, - { date: '2025-05-18', price: 3470.54 }, - { date: '2025-05-19', price: 3465.89 }, - { date: '2025-05-20', price: 3456.78 } - ] - }, - { - id: 3, - name: 'Ripple', - symbol: 'XRP', - price: 0.54, - change24h: 5.7, - volume24h: 2100000000, - marketCap: 28500000000, - priceHistory: [ - { date: '2025-05-14', price: 0.49 }, - { date: '2025-05-15', price: 0.50 }, - { date: '2025-05-16', price: 0.51 }, - { date: '2025-05-17', price: 0.52 }, - { date: '2025-05-18', price: 0.53 }, - { date: '2025-05-19', price: 0.54 }, - { date: '2025-05-20', price: 0.54 } - ] - }, - { - id: 4, - name: 'Solana', - symbol: 'SOL', - price: 156.89, - change24h: 7.2, - volume24h: 5600000000, - marketCap: 67800000000, - priceHistory: [ - { date: '2025-05-14', price: 142.34 }, - { date: '2025-05-15', price: 145.67 }, - { date: '2025-05-16', price: 148.90 }, - { date: '2025-05-17', price: 150.25 }, - { date: '2025-05-18', price: 152.30 }, - { date: '2025-05-19', price: 154.75 }, - { date: '2025-05-20', price: 156.89 } - ] - }, - { - id: 5, - name: 'Dogecoin', - symbol: 'DOGE', - price: 0.12, - change24h: -2.5, - volume24h: 980000000, - marketCap: 16500000000, - priceHistory: [ - { date: '2025-05-14', price: 0.125 }, - { date: '2025-05-15', price: 0.124 }, - { date: '2025-05-16', price: 0.123 }, - { date: '2025-05-17', price: 0.122 }, - { date: '2025-05-18', price: 0.121 }, - { date: '2025-05-19', price: 0.120 }, - { date: '2025-05-20', price: 0.120 } - ] - } -]; \ No newline at end of file diff --git a/website/src/lib/data/constants.ts b/website/src/lib/data/constants.ts new file mode 100644 index 0000000..1c2647b --- /dev/null +++ b/website/src/lib/data/constants.ts @@ -0,0 +1,9 @@ +// FILE UPLOAD +export const MAX_FILE_SIZE = 1 * 1024 * 1024; // 1MB + +// COIN CREATION COSTS +export const CREATION_FEE = 100; // $100 creation fee +export const FIXED_SUPPLY = 1000000000; // 1 billion tokens +export const STARTING_PRICE = 0.000001; // $0.000001 per token +export const INITIAL_LIQUIDITY = FIXED_SUPPLY * STARTING_PRICE; // $1000 +export const TOTAL_COST = CREATION_FEE + INITIAL_LIQUIDITY; // $1100 \ No newline at end of file diff --git a/website/src/lib/server/db/schema.ts b/website/src/lib/server/db/schema.ts index 01ecce7..6cc9f0e 100644 --- a/website/src/lib/server/db/schema.ts +++ b/website/src/lib/server/db/schema.ts @@ -17,6 +17,8 @@ export const user = pgTable("user", { precision: 19, scale: 4, }).notNull().default("10000.0000"), // 10,000 *BUSS + bio: varchar("bio", { length: 160 }).default("Hello am 48 year old man from somalia. Sorry for my bed england. I selled my wife for internet connection for play “conter stirk”"), + username: varchar("username", { length: 30 }).notNull().unique(), }); export const session = pgTable("session", { @@ -59,6 +61,7 @@ export const coin = pgTable("coin", { id: serial("id").primaryKey(), name: varchar("name", { length: 255 }).notNull(), symbol: varchar("symbol", { length: 10 }).notNull().unique(), + icon: text("icon"), // New field for coin icon creatorId: integer("creator_id").references(() => user.id, { onDelete: "set null", }), // Coin can exist even if creator is deleted initialSupply: decimal("initial_supply", { precision: 28, scale: 8 }).notNull(), circulatingSupply: decimal("circulating_supply", { precision: 28, scale: 8 }).notNull(), diff --git a/website/src/lib/server/s3.ts b/website/src/lib/server/s3.ts new file mode 100644 index 0000000..20123f4 --- /dev/null +++ b/website/src/lib/server/s3.ts @@ -0,0 +1,95 @@ +import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import { PRIVATE_B2_KEY_ID, PRIVATE_B2_APP_KEY } from '$env/static/private'; +import { PUBLIC_B2_BUCKET, PUBLIC_B2_ENDPOINT, PUBLIC_B2_REGION } from '$env/static/public'; + +const s3Client = new S3Client({ + endpoint: PUBLIC_B2_ENDPOINT, + region: PUBLIC_B2_REGION, + credentials: { + accessKeyId: PRIVATE_B2_KEY_ID, + secretAccessKey: PRIVATE_B2_APP_KEY + }, + forcePathStyle: true, + requestChecksumCalculation: 'WHEN_REQUIRED', + responseChecksumValidation: 'WHEN_REQUIRED', +}); + +export async function generatePresignedUrl(key: string, contentType: string): Promise { + const command = new PutObjectCommand({ + Bucket: PUBLIC_B2_BUCKET, + Key: key, + ContentType: contentType + }); + + return getSignedUrl(s3Client, command, { expiresIn: 3600 }); // 1 hour +} + +export async function deleteObject(key: string): Promise { + const command = new DeleteObjectCommand({ + Bucket: PUBLIC_B2_BUCKET, + Key: key + }); + + await s3Client.send(command); +} + +export async function generateDownloadUrl(key: string): Promise { + const command = new GetObjectCommand({ + Bucket: PUBLIC_B2_BUCKET, + Key: key + }); + + return getSignedUrl(s3Client, command, { expiresIn: 3600 }); +} + +export async function uploadProfilePicture( + identifier: string, // Can be user ID or a unique ID from social provider + body: Uint8Array, + contentType: string, + contentLength?: number +): Promise { + let fileExtension = contentType.split('/')[1]; + // Ensure a valid image extension or default to jpg + if (!fileExtension || !['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExtension.toLowerCase())) { + fileExtension = 'jpg'; + } + const key = `avatars/${identifier}.${fileExtension}`; + + const command = new PutObjectCommand({ + Bucket: PUBLIC_B2_BUCKET, + Key: key, + Body: body, + ContentType: contentType, + ...(contentLength && { ContentLength: contentLength }), + }); + + await s3Client.send(command); + return key; +} + +export async function uploadCoinIcon( + coinSymbol: string, + body: Uint8Array, + contentType: string, + contentLength?: number +): Promise { + let fileExtension = contentType.split('/')[1]; + if (!fileExtension || !['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExtension.toLowerCase())) { + fileExtension = 'png'; + } + const key = `coins/${coinSymbol.toLowerCase()}.${fileExtension}`; + + const command = new PutObjectCommand({ + Bucket: PUBLIC_B2_BUCKET, + Key: key, + Body: body, + ContentType: contentType, + ...(contentLength && { ContentLength: contentLength }), + }); + + await s3Client.send(command); + return key; +} + +export { s3Client }; \ No newline at end of file diff --git a/website/src/lib/stores/portfolio-data.ts b/website/src/lib/stores/portfolio-data.ts new file mode 100644 index 0000000..74ebf4a --- /dev/null +++ b/website/src/lib/stores/portfolio-data.ts @@ -0,0 +1,30 @@ +import { writable } from 'svelte/store'; + +export interface PortfolioData { + baseCurrencyBalance: number; + totalCoinValue: number; + totalValue: number; + coinHoldings: Array<{ + symbol: string; + quantity: number; + currentPrice: number; + value: number; + }>; + currency: string; +} + +export const PORTFOLIO_DATA = writable(null); + +export async function fetchPortfolioData() { + try { + const response = await fetch('/api/portfolio/total'); + if (response.ok) { + const data = await response.json(); + PORTFOLIO_DATA.set(data); + return data; + } + } catch (error) { + console.error('Failed to fetch portfolio data:', error); + } + return null; +} diff --git a/website/src/lib/stores/user-data.ts b/website/src/lib/stores/user-data.ts index 88c954f..34b0421 100644 --- a/website/src/lib/stores/user-data.ts +++ b/website/src/lib/stores/user-data.ts @@ -3,11 +3,14 @@ import { writable } from 'svelte/store'; export type User = { id: string; name: string; + username: string; email: string; isAdmin: boolean; image: string; isBanned: boolean; banReason: string | null; + avatarUrl: string | null; + bio: string; } | null; export const USER_DATA = writable(undefined); \ No newline at end of file diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index d26a5ed..2c32eb4 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -1,3 +1,4 @@ +import { PUBLIC_B2_BUCKET, PUBLIC_B2_ENDPOINT } from "$env/static/public"; import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; @@ -25,3 +26,20 @@ export function getTimeBasedGreeting(name: string): string { return `Good night, ${name}`; } } + +export function getPublicUrl(key: string | null): string | null { + if (!key) return null; + return `${PUBLIC_B2_ENDPOINT}/${PUBLIC_B2_BUCKET}/${key}`; +} + +export function debounce(func: (...args: any[]) => void, wait: number) { + let timeout: number | undefined; + return function executedFunction(...args: any[]) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} \ No newline at end of file diff --git a/website/src/lib/utils/random.ts b/website/src/lib/utils/random.ts new file mode 100644 index 0000000..c8522ee --- /dev/null +++ b/website/src/lib/utils/random.ts @@ -0,0 +1,9 @@ +const adjectives = ['happy', 'lucky', 'sunny', 'clever', 'brave', 'bright', 'cool', 'wild', 'calm', 'kind']; +const nouns = ['panda', 'tiger', 'whale', 'eagle', 'lion', 'wolf', 'bear', 'fox', 'deer', 'seal']; + +export function generateUsername(): string { + const adj = adjectives[Math.floor(Math.random() * adjectives.length)]; + const noun = nouns[Math.floor(Math.random() * nouns.length)]; + const number = Math.floor(Math.random() * 9999); + return `${adj}_${noun}${number}`; +} diff --git a/website/src/routes/+layout.server.ts b/website/src/routes/+layout.server.ts index 2752ac3..43caefa 100644 --- a/website/src/routes/+layout.server.ts +++ b/website/src/routes/+layout.server.ts @@ -1,6 +1,4 @@ import { auth } from '$lib/auth'; -import { db } from '$lib/server/db'; -import { eq } from 'drizzle-orm'; import type { LayoutServerLoad } from './$types'; import { dev } from '$app/environment'; diff --git a/website/src/routes/+layout.svelte b/website/src/routes/+layout.svelte index 15a0b49..cccf9a0 100644 --- a/website/src/routes/+layout.svelte +++ b/website/src/routes/+layout.svelte @@ -2,12 +2,15 @@ import '../app.css'; import * as Sidebar from '$lib/components/ui/sidebar/index.js'; + import { Toaster } from '$lib/components/ui/sonner'; + import AppSidebar from '$lib/components/self/AppSidebar.svelte'; import { USER_DATA } from '$lib/stores/user-data'; import { onMount } from 'svelte'; import { invalidateAll } from '$app/navigation'; import { ModeWatcher } from 'mode-watcher'; + import { page } from '$app/state'; let { data, children } = $props<{ data: { userSession?: any }; @@ -70,6 +73,7 @@ + @@ -81,7 +85,13 @@
-

test

+

+ {#if page.route.id === '/coin/create'} + Coin: Create + {:else} + test + {/if} +

diff --git a/website/src/routes/+page.svelte b/website/src/routes/+page.svelte index 524c069..0fbc77e 100644 --- a/website/src/routes/+page.svelte +++ b/website/src/routes/+page.svelte @@ -1,13 +1,53 @@ @@ -34,83 +74,105 @@

- + {#if loading} +
+
+
Loading market data...
+
+
+ {:else if coins.length === 0} +
+
+
No coins available
+

Be the first to create a coin!

+
+
+ {:else} + -
-

Market Overview

- - - - - - Name - Price - 24h Change - - - - - - {#each coins as coin} +
+

Market Overview

+ + + + - - - {coin.name} ({coin.symbol}) - - - ${coin.price.toLocaleString(undefined, { - minimumFractionDigits: coin.price < 1 ? 3 : 2, - maximumFractionDigits: coin.price < 1 ? 3 : 2 - })} - - = 0 ? 'success' : 'destructive'}> - {coin.change24h >= 0 ? '+' : ''}{coin.change24h}% - - - - + Name + Price + 24h Change + + - {/each} - - - - -
+ + + {#each coins as coin} + + + + {#if coin.icon} + {coin.name} + {/if} + {coin.name} (*{coin.symbol}) + + + ${formatPrice(coin.price)} + + = 0 ? 'success' : 'destructive'}> + {coin.change24h >= 0 ? '+' : ''}{coin.change24h.toFixed(2)}% + + + + + + {/each} + +
+
+
+
+ {/if} diff --git a/website/src/routes/api/coin/[coinSymbol]/+server.ts b/website/src/routes/api/coin/[coinSymbol]/+server.ts new file mode 100644 index 0000000..37a6f58 --- /dev/null +++ b/website/src/routes/api/coin/[coinSymbol]/+server.ts @@ -0,0 +1,73 @@ +import { error, json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { coin, user, priceHistory } from '$lib/server/db/schema'; +import { eq, desc } from 'drizzle-orm'; + +export async function GET({ params }) { + const { coinSymbol } = params; + + if (!coinSymbol) { + throw error(400, 'Coin symbol is required'); + } + + const normalizedSymbol = coinSymbol.toUpperCase(); + + const [coinData] = await db + .select({ + id: coin.id, + name: coin.name, + symbol: coin.symbol, + creatorId: coin.creatorId, + creatorName: user.name, + creatorUsername: user.username, + creatorBio: user.bio, + creatorImage: user.image, + initialSupply: coin.initialSupply, + circulatingSupply: coin.circulatingSupply, + currentPrice: coin.currentPrice, + marketCap: coin.marketCap, + icon: coin.icon, + volume24h: coin.volume24h, + change24h: coin.change24h, + poolCoinAmount: coin.poolCoinAmount, + poolBaseCurrencyAmount: coin.poolBaseCurrencyAmount, + createdAt: coin.createdAt, + isListed: coin.isListed + }) + .from(coin) + .leftJoin(user, eq(coin.creatorId, user.id)) + .where(eq(coin.symbol, normalizedSymbol)) + .limit(1); + + if (!coinData) { + throw error(404, 'Coin not found'); + } + + const priceHistoryData = await db + .select({ + price: priceHistory.price, + timestamp: priceHistory.timestamp + }) + .from(priceHistory) + .where(eq(priceHistory.coinId, coinData.id)) + .orderBy(desc(priceHistory.timestamp)) + .limit(720); + + return json({ + coin: { + ...coinData, + currentPrice: Number(coinData.currentPrice), + marketCap: Number(coinData.marketCap), + volume24h: Number(coinData.volume24h || 0), + change24h: Number(coinData.change24h || 0), + initialSupply: Number(coinData.initialSupply), + circulatingSupply: Number(coinData.circulatingSupply), + poolCoinAmount: Number(coinData.poolCoinAmount), + poolBaseCurrencyAmount: Number(coinData.poolBaseCurrencyAmount) + }, + priceHistory: priceHistoryData.map(p => ({ + price: Number(p.price), + timestamp: p.timestamp + })) + }); +} diff --git a/website/src/routes/api/coin/create/+server.ts b/website/src/routes/api/coin/create/+server.ts new file mode 100644 index 0000000..2b11ca0 --- /dev/null +++ b/website/src/routes/api/coin/create/+server.ts @@ -0,0 +1,143 @@ +import { auth } from '$lib/auth'; +import { error, json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { coin, userPortfolio, user, priceHistory } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; +import { uploadCoinIcon } from '$lib/server/s3'; +import { CREATION_FEE, FIXED_SUPPLY, STARTING_PRICE, INITIAL_LIQUIDITY, TOTAL_COST, MAX_FILE_SIZE } from '$lib/data/constants'; + +function validateInputs(name: string, symbol: string, iconFile: File | null) { + if (!name || name.length < 2 || name.length > 255) { + throw error(400, 'Name must be between 2 and 255 characters'); + } + + if (!symbol || symbol.length < 2 || symbol.length > 10) { + throw error(400, 'Symbol must be between 2 and 10 characters'); + } + + if (iconFile && iconFile.size > MAX_FILE_SIZE) { + throw error(400, 'Icon file must be smaller than 1MB'); + } +} + +async function validateUserBalance(userId: number) { + const [userData] = await db + .select({ baseCurrencyBalance: user.baseCurrencyBalance }) + .from(user) + .where(eq(user.id, userId)) + .limit(1); + + if (!userData) { + throw error(404, 'User not found'); + } + + const currentBalance = Number(userData.baseCurrencyBalance); + if (currentBalance < TOTAL_COST) { + throw error(400, `Insufficient funds. You need $${TOTAL_COST.toFixed(2)} but only have $${currentBalance.toFixed(2)}.`); + } + + return currentBalance; +} + +async function validateSymbolUnique(symbol: string) { + const existingCoin = await db.select().from(coin).where(eq(coin.symbol, symbol)).limit(1); + if (existingCoin.length > 0) { + throw error(400, 'A coin with this symbol already exists'); + } +} + +async function handleIconUpload(iconFile: File | null, symbol: string): Promise { + if (!iconFile || iconFile.size === 0) { + return null; + } + + const arrayBuffer = await iconFile.arrayBuffer(); + return await uploadCoinIcon( + symbol, + new Uint8Array(arrayBuffer), + iconFile.type, + iconFile.size + ); +} + +export async function POST({ request }) { + const session = await auth.api.getSession({ + headers: request.headers + }); + + if (!session?.user) { + throw error(401, 'Not authenticated'); + } + + const formData = await request.formData(); + const name = formData.get('name') as string; + const symbol = formData.get('symbol') as string; + const iconFile = formData.get('icon') as File | null; + + const normalizedSymbol = symbol?.toUpperCase(); + const userId = Number(session.user.id); + + validateInputs(name, normalizedSymbol, iconFile); + + const [currentBalance] = await Promise.all([ + validateUserBalance(userId), + validateSymbolUnique(normalizedSymbol) + ]); + + let iconKey: string | null = null; + try { + iconKey = await handleIconUpload(iconFile, normalizedSymbol); + } catch (e) { + console.error('Icon upload failed, continuing without icon:', e); + } + + let createdCoin: any; + await db.transaction(async (tx) => { + await tx.update(user) + .set({ + baseCurrencyBalance: (currentBalance - TOTAL_COST).toString(), + updatedAt: new Date() + }) + .where(eq(user.id, userId)); + + const [newCoin] = await tx.insert(coin).values({ + name, + symbol: normalizedSymbol, + icon: iconKey, + creatorId: userId, + initialSupply: FIXED_SUPPLY.toString(), + circulatingSupply: FIXED_SUPPLY.toString(), + currentPrice: STARTING_PRICE.toString(), + marketCap: (FIXED_SUPPLY * STARTING_PRICE).toString(), + poolCoinAmount: FIXED_SUPPLY.toString(), + poolBaseCurrencyAmount: INITIAL_LIQUIDITY.toString() + }).returning(); + + createdCoin = newCoin; + + await tx.insert(userPortfolio).values({ + userId, + coinId: newCoin.id, + quantity: FIXED_SUPPLY.toString() + }); + + await tx.insert(priceHistory).values({ + coinId: newCoin.id, + price: STARTING_PRICE.toString() + }); + }); + + return json({ + success: true, + coin: { + id: createdCoin.id, + name: createdCoin.name, + symbol: createdCoin.symbol, + icon: createdCoin.icon + }, + feePaid: CREATION_FEE, + liquidityDeposited: INITIAL_LIQUIDITY, + initialPrice: STARTING_PRICE, + supply: FIXED_SUPPLY + }); +} diff --git a/website/src/routes/api/coins/top/+server.ts b/website/src/routes/api/coins/top/+server.ts new file mode 100644 index 0000000..f322ad7 --- /dev/null +++ b/website/src/routes/api/coins/top/+server.ts @@ -0,0 +1,37 @@ +import { json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { coin } from '$lib/server/db/schema'; +import { desc, eq } from 'drizzle-orm'; + +export async function GET() { + const topCoins = await db + .select({ + id: coin.id, + name: coin.name, + symbol: coin.symbol, + icon: coin.icon, + currentPrice: coin.currentPrice, + marketCap: coin.marketCap, + volume24h: coin.volume24h, + change24h: coin.change24h, + isListed: coin.isListed + }) + .from(coin) + .where(eq(coin.isListed, true)) + .orderBy(desc(coin.marketCap)) + .limit(20); + + return json({ + coins: topCoins.map(c => ({ + id: c.id, + name: c.name, + symbol: c.symbol, + icon: c.icon, + price: Number(c.currentPrice), + marketCap: Number(c.marketCap), + volume24h: Number(c.volume24h || 0), + change24h: Number(c.change24h || 0), + isListed: c.isListed + })) + }); +} diff --git a/website/src/routes/api/portfolio/total/+server.ts b/website/src/routes/api/portfolio/total/+server.ts new file mode 100644 index 0000000..6f0fdab --- /dev/null +++ b/website/src/routes/api/portfolio/total/+server.ts @@ -0,0 +1,62 @@ +import { auth } from '$lib/auth'; +import { error, json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { user, userPortfolio, coin } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; + +export async function GET({ request }) { + const session = await auth.api.getSession({ + headers: request.headers + }); + + if (!session?.user) { + throw error(401, 'Not authenticated'); + } + + const userId = Number(session.user.id); + + const [userData] = await db + .select({ baseCurrencyBalance: user.baseCurrencyBalance }) + .from(user) + .where(eq(user.id, userId)) + .limit(1); + + if (!userData) { + throw error(404, 'User not found'); + } + + const holdings = await db + .select({ + quantity: userPortfolio.quantity, + currentPrice: coin.currentPrice, + symbol: coin.symbol + }) + .from(userPortfolio) + .innerJoin(coin, eq(userPortfolio.coinId, coin.id)) + .where(eq(userPortfolio.userId, userId)); + + let totalCoinValue = 0; + const coinHoldings = holdings.map(holding => { + const quantity = Number(holding.quantity); + const price = Number(holding.currentPrice); + const value = quantity * price; + totalCoinValue += value; + + return { + symbol: holding.symbol, + quantity, + currentPrice: price, + value + }; + }); + + const baseCurrencyBalance = Number(userData.baseCurrencyBalance); + + return json({ + baseCurrencyBalance, + totalCoinValue, + totalValue: baseCurrencyBalance + totalCoinValue, + coinHoldings, + currency: '$' + }); +} diff --git a/website/src/routes/api/settings/+server.ts b/website/src/routes/api/settings/+server.ts new file mode 100644 index 0000000..f1cd4a9 --- /dev/null +++ b/website/src/routes/api/settings/+server.ts @@ -0,0 +1,71 @@ +import { auth } from '$lib/auth'; +import { uploadProfilePicture } from '$lib/server/s3'; +import { error, json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { user } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; +import { MAX_FILE_SIZE } from '$lib/data/constants'; + +function validateInputs(name: string, bio: string, username: string, avatarFile: File | null) { + if (name && name.length < 1) { + throw error(400, 'Name cannot be empty'); + } + + if (bio && bio.length > 160) { + throw error(400, 'Bio must be 160 characters or less'); + } + + if (username && (username.length < 3 || username.length > 30)) { + throw error(400, 'Username must be between 3 and 30 characters'); + } + + if (avatarFile && avatarFile.size > MAX_FILE_SIZE) { + throw error(400, 'Avatar file must be smaller than 1MB'); + } +} + +export async function POST({ request }) { + const session = await auth.api.getSession({ + headers: request.headers + }); + + if (!session?.user) { + throw error(401, 'Not authenticated'); + } + + const formData = await request.formData(); + const name = formData.get('name') as string; + const bio = formData.get('bio') as string; + const username = formData.get('username') as string; + const avatarFile = formData.get('avatar') as File | null; + + validateInputs(name, bio, username, avatarFile); + + const updates: Record = { + name, + bio, + username, + updatedAt: new Date() + }; + + if (avatarFile && avatarFile.size > 0) { + try { + const arrayBuffer = await avatarFile.arrayBuffer(); + const key = await uploadProfilePicture( + session.user.id, + new Uint8Array(arrayBuffer), + avatarFile.type, + avatarFile.size + ); + updates.image = key; + } catch (e) { + console.error('Avatar upload failed, continuing without update:', e); + } + } + + await db.update(user) + .set(updates) + .where(eq(user.id, Number(session.user.id))); + + return json({ success: true }); +} diff --git a/website/src/routes/api/settings/check-username/+server.ts b/website/src/routes/api/settings/check-username/+server.ts new file mode 100644 index 0000000..f03f501 --- /dev/null +++ b/website/src/routes/api/settings/check-username/+server.ts @@ -0,0 +1,17 @@ +import { json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { user } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; + +export async function GET({ url }) { + const username = url.searchParams.get('username'); + if (!username) { + return json({ available: false }); + } + + const exists = await db.query.user.findFirst({ + where: eq(user.username, username) + }); + + return json({ available: !exists }); +} diff --git a/website/src/routes/api/user/[userId]/image/+server.ts b/website/src/routes/api/user/[userId]/image/+server.ts new file mode 100644 index 0000000..4f5c1c6 --- /dev/null +++ b/website/src/routes/api/user/[userId]/image/+server.ts @@ -0,0 +1,28 @@ +import { error, json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { user } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; +import { getPublicUrl } from '$lib/utils'; + +export async function GET({ params }) { + const { userId } = params; + + try { + const [userData] = await db + .select({ image: user.image }) + .from(user) + .where(eq(user.id, Number(userId))) + .limit(1); + + if (!userData) { + throw error(404, 'User not found'); + } + + const url = getPublicUrl(userData.image); + + return json({ url }); + } catch (e) { + console.error('Failed to get user image:', e); + throw error(500, 'Failed to get user image'); + } +} diff --git a/website/src/routes/coin/[coinSymbol]/+page.svelte b/website/src/routes/coin/[coinSymbol]/+page.svelte index ddbeba8..3053877 100644 --- a/website/src/routes/coin/[coinSymbol]/+page.svelte +++ b/website/src/routes/coin/[coinSymbol]/+page.svelte @@ -1,144 +1,452 @@ -
- {#if coin} -
-
-

{coin.name} ({coin.symbol})

- = 0 ? 'success' : 'destructive'} class="text-lg"> - {coin.change24h >= 0 ? '+' : ''}{coin.change24h}% - + + {coin ? `${coin.name} (${coin.symbol})` : 'Loading...'} - Rugplay + + +{#if loading} +
+
+
+
Loading coin data...
-

- ${coin.price.toLocaleString(undefined, { - minimumFractionDigits: coin.price < 1 ? 3 : 2, - maximumFractionDigits: coin.price < 1 ? 3 : 2 - })} -

+
+
+{:else if !coin} +
+
+
+
Coin not found
+ +
+
+
+{:else} +
+ +
+
+
+
+ {#if coin.icon} + {coin.name} + {:else} +
+ {coin.symbol.slice(0, 2)} +
+ {/if} +
+
+

{coin.name}

+
+ *{coin.symbol} + {#if !coin.isListed} + Delisted + {/if} +
+
+
+
+

+ ${formatPrice(coin.currentPrice)} +

+
+ {#if coin.change24h >= 0} + + {:else} + + {/if} + = 0 ? 'success' : 'destructive'}> + {coin.change24h >= 0 ? '+' : ''}{coin.change24h.toFixed(2)}% + +
+
+
+ + + {#if coin.creatorName} +
+ Created by + + + goto(`/user/${coin.creatorId}`)} + > + + + {coin.creatorName.charAt(0)} + + {coin.creatorName} (@{coin.creatorUsername}) + + +
+ + + {coin.creatorName.charAt(0)} + +
+

{coin.creatorName}

+

@{coin.creatorUsername}

+ {#if coin.creatorBio} +

{coin.creatorBio}

+ {/if} +
+ + + Joined {new Date(coin.createdAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long' + })} + +
+
+
+
+
+
+ {/if}
- - - Price Chart - - -
-
-
+ +
+ +
+ + + + + Price Chart + + + +
+
+
+
-
+ +
+ + + + Trade {coin.symbol} + + +
+ + +
+
+
+ + + + + Liquidity Pool + + +
+
+

Pool Composition

+
+
+ {coin.symbol}: + {formatSupply(coin.poolCoinAmount)} +
+
+ Base Currency: + ${coin.poolBaseCurrencyAmount.toLocaleString()} +
+
+
+
+

Pool Stats

+
+
+ Total Liquidity: + ${(coin.poolBaseCurrencyAmount * 2).toLocaleString()} +
+
+ Price Impact: + Low +
+
+
+
+
+
+
+
+ + +
+ - - Market Cap + + + + Market Cap + - -

${(coin.marketCap / 1000000000).toFixed(2)}B

+ +

{formatMarketCap(coin.marketCap)}

+ - - 24h Volume + + + + 24h Volume + - -

${(coin.volume24h / 1000000000).toFixed(2)}B

+ +

{formatMarketCap(coin.volume24h)}

+ - - 24h Change + + + + Circulating Supply + - -

- = 0 ? 'success' : 'destructive'} class="text-lg"> - {coin.change24h >= 0 ? '+' : ''}{coin.change24h}% - + +

{formatSupply(coin.circulatingSupply)}

+

+ of {formatSupply(coin.initialSupply)} total

+ + + + + 24h Change + + +
+ {#if coin.change24h >= 0} + + {:else} + + {/if} + = 0 ? 'success' : 'destructive'} class="text-sm"> + {coin.change24h >= 0 ? '+' : ''}{coin.change24h.toFixed(2)}% + +
+
+
- {:else} -

Coin not found

- {/if} -
+
+{/if} diff --git a/website/src/routes/coin/[coinSymbol]/+page.ts b/website/src/routes/coin/[coinSymbol]/+page.ts new file mode 100644 index 0000000..061fb84 --- /dev/null +++ b/website/src/routes/coin/[coinSymbol]/+page.ts @@ -0,0 +1,5 @@ +export async function load({ params }) { + return { + coinSymbol: params.coinSymbol + }; +} diff --git a/website/src/routes/coin/create/+page.svelte b/website/src/routes/coin/create/+page.svelte new file mode 100644 index 0000000..1095f67 --- /dev/null +++ b/website/src/routes/coin/create/+page.svelte @@ -0,0 +1,329 @@ + + + + Create Coin - Rugplay + + +
+
+ +
+ + + Coin Details + + +
+ +
+ +
+ +

+ {#if iconError} + {iconError} + {:else if iconFile} + {iconFile.name} ({(iconFile.size / 1024).toFixed(2)} KB) + {:else} + Click to upload your coin's icon (PNG or JPG, max 1MB) + {/if} +

+
+
+ + +
+ + + {#if nameError} +

{nameError}

+ {:else} +

+ Choose a memorable name for your cryptocurrency +

+ {/if} +
+ + +
+ +
+ * + +
+ {#if symbolError} +

{symbolError}

+ {:else} +

+ Short identifier for your coin (e.g., BTC for Bitcoin). Will be displayed as *{symbol || + 'SYMBOL'} +

+ {/if} +
+ + + + + +

Fair Launch Settings

+
+

• Total Supply: 1,000,000,000 tokens

+

• Starting Price: $0.000001 per token

+

• You receive 100% of the supply

+

• Initial Market Cap: $1,000

+

+ These settings ensure a fair start for all traders. The price will increase + naturally as people buy tokens. +

+
+
+
+ + + +
+
+
+
+ + +
+ + {#if $PORTFOLIO_DATA} + + +
+ Cost Summary +
+ Balance: + + ${$PORTFOLIO_DATA.baseCurrencyBalance.toLocaleString()} + +
+
+
+ +
+ Creation Fee + ${CREATION_FEE} +
+
+ Initial Liquidity + ${INITIAL_LIQUIDITY} +
+ +
+ Total Cost + ${TOTAL_COST} +
+
+
+ {/if} + + + + + What Happens After Launch? + + +
+
+
+ 1 +
+
+

Fair Distribution

+

+ Everyone starts buying at the same price - no pre-sales or hidden allocations +

+
+
+
+
+ 2 +
+
+

Price Discovery

+

+ Token price increases automatically as more people buy, following a bonding curve +

+
+
+
+
+ 3 +
+
+

Instant Trading

+

+ Trading begins immediately - buy, sell, or distribute your tokens as you wish +

+
+
+
+
+
+
+
+
+ + diff --git a/website/src/routes/settings/+page.server.ts b/website/src/routes/settings/+page.server.ts new file mode 100644 index 0000000..ce5ed7f --- /dev/null +++ b/website/src/routes/settings/+page.server.ts @@ -0,0 +1,12 @@ +import { auth } from '$lib/auth'; +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; + +export const load: PageServerLoad = async (event) => { + const session = await auth.api.getSession({ + headers: event.request.headers + }); + if (!session?.user) throw error(401, 'Not authenticated'); + + return { user: session.user }; +}; diff --git a/website/src/routes/settings/+page.svelte b/website/src/routes/settings/+page.svelte new file mode 100644 index 0000000..064ec09 --- /dev/null +++ b/website/src/routes/settings/+page.svelte @@ -0,0 +1,213 @@ + + +
+

Settings

+ + + Profile Settings + Update your profile information + + +
+
e.key === 'Enter' && handleAvatarClick()} + > + + + ? + +
+ Change +
+
+
+

{name}

+

@{username}

+
+
+ + + +
+
+ + +
+ +
+ +
+ @ + +
+ {#if checkingUsername} + Checking… + {:else if username !== initialUsername} + {#if usernameAvailable} + + {:else} + Taken + {/if} + {/if} +
+
+

+ Only letters, numbers, underscores. 3–30 characters. +

+
+ +
+ +