From 38c6781c13d2b74c2d7358b4e9014f3803c680e5 Mon Sep 17 00:00:00 2001 From: Felix <60808107+ItsFelix5@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:19:42 +0200 Subject: [PATCH 1/7] Update +page.svelte --- website/src/routes/hopium/[id]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/routes/hopium/[id]/+page.svelte b/website/src/routes/hopium/[id]/+page.svelte index 7c97a04..07f4480 100644 --- a/website/src/routes/hopium/[id]/+page.svelte +++ b/website/src/routes/hopium/[id]/+page.svelte @@ -415,7 +415,7 @@ size="lg" disabled={!customBetAmount || Number(customBetAmount) <= 0 || - Number(customBetAmount) >= userBalance || + Number(customBetAmount) > userBalance || placingBet || question.aiResolution !== null} onclick={placeBet} From 861bb01a3187477ea3fc2977dfaa7bf0a25267a1 Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Sat, 14 Jun 2025 19:06:22 +0600 Subject: [PATCH 2/7] feat: image compression w/ sharp --- website/package-lock.json | 1600 ++++++++++++++++- website/package.json | 3 +- website/src/lib/auth.ts | 3 +- website/src/lib/server/image.ts | 39 + website/src/lib/server/s3.ts | 27 +- website/src/routes/api/coin/create/+server.ts | 3 +- website/src/routes/api/settings/+server.ts | 3 +- 7 files changed, 1626 insertions(+), 52 deletions(-) create mode 100644 website/src/lib/server/image.ts diff --git a/website/package-lock.json b/website/package-lock.json index 335631a..c5b45f5 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@aws-sdk/client-s3": "^3.815.0", "@aws-sdk/s3-request-presigner": "^3.815.0", + "@sveltejs/adapter-node": "^5.2.12", "@tailwindcss/postcss": "^4.1.7", "@tailwindcss/typography": "^0.5.16", "@visx/scale": "^3.12.0", @@ -17,17 +18,20 @@ "better-auth": "^1.2.8", "canvas-confetti": "^1.9.3", "drizzle-orm": "^0.33.0", + "express": "^5.1.0", "lightweight-charts": "^5.0.7", "lucide-svelte": "^0.511.0", "mode-watcher": "^1.0.7", "openai": "^4.103.0", "postgres": "^3.4.4", "redis": "^5.1.0", + "sharp": "^0.34.2", "svelte-apexcharts": "^1.0.2", "svelte-confetti": "^2.3.1", "svelte-lightweight-charts": "^2.2.0" }, "devDependencies": { + "@internationalized/date": "^3.8.1", "@lucide/svelte": "^0.482.0", "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/kit": "^2.0.0", @@ -35,7 +39,7 @@ "@types/canvas-confetti": "^1.9.0", "@types/node": "^22.15.21", "autoprefixer": "^10.4.20", - "bits-ui": "^2.1.0", + "bits-ui": "^2.5.0", "clsx": "^2.1.1", "drizzle-kit": "^0.22.0", "prettier": "^3.3.2", @@ -50,6 +54,9 @@ "tw-animate-css": "^1.3.0", "typescript": "^5.0.0", "vite": "^5.4.11" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "*" } }, "node_modules/@alloc/quick-lru": { @@ -870,6 +877,15 @@ "node_modules/@better-fetch/fetch": { "version": "1.1.18" }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild-kit/core-utils": { "version": "3.3.2", "dev": true, @@ -980,11 +996,385 @@ "version": "1.1.28", "license": "MIT" }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz", + "integrity": "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz", + "integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", + "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz", + "integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz", + "integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz", + "integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz", + "integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz", + "integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz", + "integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz", + "integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz", + "integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz", + "integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz", + "integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@internationalized/date": { "version": "3.8.1", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/helpers": "^0.5.0" } @@ -1116,7 +1506,6 @@ }, "node_modules/@polka/url": { "version": "1.0.0-next.29", - "dev": true, "license": "MIT" }, "node_modules/@redis/bloom": { @@ -1169,12 +1558,335 @@ "@redis/client": "^5.1.0" } }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", + "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", + "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", + "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.41.0", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1837,9 +2549,22 @@ "@sveltejs/kit": "^2.0.0" } }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz", + "integrity": "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, "node_modules/@sveltejs/kit": { "version": "2.21.1", - "dev": true, "license": "MIT", "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", @@ -1869,7 +2594,6 @@ }, "node_modules/@sveltejs/vite-plugin-svelte": { "version": "4.0.4", - "dev": true, "license": "MIT", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", @@ -1889,7 +2613,6 @@ }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { "version": "3.0.1", - "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.7" @@ -1953,7 +2676,6 @@ "version": "0.5.17", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -2054,7 +2776,6 @@ }, "node_modules/@types/cookie": { "version": "0.6.0", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-array": { @@ -2125,6 +2846,11 @@ "form-data": "^4.0.0" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" + }, "node_modules/@visx/scale": { "version": "3.12.0", "license": "MIT", @@ -2201,6 +2927,37 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.14.1", "license": "MIT", @@ -2326,9 +3083,10 @@ } }, "node_modules/bits-ui": { - "version": "2.2.0", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.6.0.tgz", + "integrity": "sha512-vb0J2C7Jfh/ePnoRwPTVcnMl2JJFFmv3zv4Ssv/DU18WkB5xgIU9jEmfd3IcR7mTjIpFZgYfil1j4w6Fa+OoVw==", "dev": true, - "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/dom": "^1.7.0", @@ -2350,6 +3108,25 @@ "svelte": "^5.33.0" } }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/bowser": { "version": "2.11.0", "license": "MIT" @@ -2390,6 +3167,14 @@ "dev": true, "license": "MIT" }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "license": "MIT", @@ -2401,6 +3186,21 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001718", "dev": true, @@ -2464,6 +3264,43 @@ "node": ">=0.10.0" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "license": "MIT", @@ -2474,14 +3311,45 @@ "node": ">= 0.8" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie": { "version": "0.6.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/css.escape": { "version": "1.5.1", "dev": true, @@ -2577,7 +3445,6 @@ }, "node_modules/debug": { "version": "4.4.1", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2593,7 +3460,6 @@ }, "node_modules/deepmerge": { "version": "4.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2617,6 +3483,14 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "license": "Apache-2.0", @@ -2626,7 +3500,6 @@ }, "node_modules/devalue": { "version": "5.1.1", - "dev": true, "license": "MIT" }, "node_modules/drizzle-kit": { @@ -2773,11 +3646,24 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/electron-to-chromium": { "version": "1.5.155", "dev": true, "license": "ISC" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "license": "MIT", @@ -2882,6 +3768,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/esm-env": { "version": "1.2.2", "license": "MIT" @@ -2893,6 +3784,19 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "license": "MIT", @@ -2900,6 +3804,74 @@ "node": ">=6" } }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fancy-canvas": { "version": "2.1.0", "license": "MIT" @@ -2926,7 +3898,6 @@ }, "node_modules/fdir": { "version": "6.4.4", - "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -2937,6 +3908,22 @@ } } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/form-data": { "version": "4.0.2", "license": "MIT", @@ -2965,6 +3952,14 @@ "node": ">= 12.20" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "dev": true, @@ -2977,6 +3972,27 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", @@ -3075,6 +4091,29 @@ "node": ">= 0.4" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "license": "MIT", @@ -3082,6 +4121,17 @@ "ms": "^2.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-meta-resolve": { "version": "4.1.0", "dev": true, @@ -3091,6 +4141,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/inline-style-parser": { "version": "0.2.4", "license": "MIT" @@ -3102,6 +4157,43 @@ "node": ">=12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "node_modules/is-reference": { "version": "3.0.3", "license": "MIT", @@ -3125,7 +4217,6 @@ }, "node_modules/kleur": { "version": "4.1.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3226,6 +4317,25 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mime-db": { "version": "1.52.0", "license": "MIT", @@ -3330,7 +4440,6 @@ }, "node_modules/mri": { "version": "1.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -3338,7 +4447,6 @@ }, "node_modules/mrmime": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -3377,6 +4485,14 @@ "node": "^18.0.0 || >=20.0.0" } }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "funding": [ @@ -3425,6 +4541,36 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/openai": { "version": "4.103.0", "license": "Apache-2.0", @@ -3464,6 +4610,27 @@ "version": "5.26.5", "license": "MIT" }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -3472,9 +4639,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=12" }, @@ -3624,6 +4788,18 @@ } } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pvtsutils": { "version": "1.3.6", "license": "MIT", @@ -3638,6 +4814,42 @@ "node": ">=6.0.0" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/readdirp": { "version": "4.1.2", "dev": true, @@ -3664,6 +4876,25 @@ "node": ">= 18" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "dev": true, @@ -3678,7 +4909,6 @@ }, "node_modules/rollup": { "version": "4.41.0", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.7" @@ -3714,10 +4944,37 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", + "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/rou3": { "version": "0.5.1", "license": "MIT" }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/runed": { "version": "0.28.0", "dev": true, @@ -3735,7 +4992,6 @@ }, "node_modules/sade": { "version": "1.8.1", - "dev": true, "license": "MIT", "dependencies": { "mri": "^1.1.0" @@ -3744,13 +5000,222 @@ "node": ">=6" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "license": "MIT" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sharp": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz", + "integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.2", + "@img/sharp-darwin-x64": "0.34.2", + "@img/sharp-libvips-darwin-arm64": "1.1.0", + "@img/sharp-libvips-darwin-x64": "1.1.0", + "@img/sharp-libvips-linux-arm": "1.1.0", + "@img/sharp-libvips-linux-arm64": "1.1.0", + "@img/sharp-libvips-linux-ppc64": "1.1.0", + "@img/sharp-libvips-linux-s390x": "1.1.0", + "@img/sharp-libvips-linux-x64": "1.1.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", + "@img/sharp-libvips-linuxmusl-x64": "1.1.0", + "@img/sharp-linux-arm": "0.34.2", + "@img/sharp-linux-arm64": "0.34.2", + "@img/sharp-linux-s390x": "0.34.2", + "@img/sharp-linux-x64": "0.34.2", + "@img/sharp-linuxmusl-arm64": "0.34.2", + "@img/sharp-linuxmusl-x64": "0.34.2", + "@img/sharp-wasm32": "0.34.2", + "@img/sharp-win32-arm64": "0.34.2", + "@img/sharp-win32-ia32": "0.34.2", + "@img/sharp-win32-x64": "0.34.2" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sirv": { "version": "3.0.1", - "dev": true, "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -3785,6 +5250,14 @@ "source-map": "^0.6.0" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/strnum": { "version": "1.1.2", "funding": [ @@ -3802,6 +5275,17 @@ "inline-style-parser": "0.2.4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/svelte": { "version": "5.33.4", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.4.tgz", @@ -4071,9 +5555,16 @@ "node": ">=18" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/totalist": { "version": "3.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4095,6 +5586,38 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.8.3", "license": "Apache-2.0", @@ -4114,6 +5637,14 @@ "version": "6.21.0", "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "dev": true, @@ -4158,9 +5689,16 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "5.4.19", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -4218,7 +5756,6 @@ }, "node_modules/vite/node_modules/esbuild": { "version": "0.21.5", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4258,7 +5795,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4270,7 +5806,6 @@ }, "node_modules/vitefu": { "version": "1.0.6", - "dev": true, "license": "MIT", "workspaces": [ "tests/deps/*", @@ -4304,6 +5839,11 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, "node_modules/yallist": { "version": "5.0.0", "license": "BlueOak-1.0.0", diff --git a/website/package.json b/website/package.json index 7bb862b..efacfac 100644 --- a/website/package.json +++ b/website/package.json @@ -58,6 +58,7 @@ "openai": "^4.103.0", "postgres": "^3.4.4", "redis": "^5.1.0", + "sharp": "^0.34.2", "svelte-apexcharts": "^1.0.2", "svelte-confetti": "^2.3.1", "svelte-lightweight-charts": "^2.2.0" @@ -65,4 +66,4 @@ "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "*" } -} \ No newline at end of file +} diff --git a/website/src/lib/auth.ts b/website/src/lib/auth.ts index f4692cf..523acd2 100644 --- a/website/src/lib/auth.ts +++ b/website/src/lib/auth.ts @@ -42,8 +42,7 @@ export const auth = betterAuth({ s3ImageKey = await uploadProfilePicture( profile.sub, new Uint8Array(arrayBuffer), - blob.type, - blob.size + blob.type || 'image/jpeg' ); } } catch (error) { diff --git a/website/src/lib/server/image.ts b/website/src/lib/server/image.ts new file mode 100644 index 0000000..91e2709 --- /dev/null +++ b/website/src/lib/server/image.ts @@ -0,0 +1,39 @@ +import sharp from 'sharp'; + +const MAX_SIZE = 128; +const WEBP_QUALITY = 50; + +export interface ProcessedImage { + buffer: Buffer; + contentType: string; + size: number; +} + +export async function processImage( + inputBuffer: Buffer, +): Promise { + try { + const image = sharp(inputBuffer, { animated: true }); + + const processedBuffer = await image + .resize(MAX_SIZE, MAX_SIZE, { + fit: 'inside', + withoutEnlargement: true + }) + .webp({ + quality: WEBP_QUALITY, + effort: 6 + }) + .toBuffer(); + + return { + buffer: processedBuffer, + contentType: 'image/webp', + size: processedBuffer.length + }; + + } catch (error) { + console.error('Image processing failed:', error); + throw new Error('Failed to process image'); + } +} diff --git a/website/src/lib/server/s3.ts b/website/src/lib/server/s3.ts index 3e96702..f4379f0 100644 --- a/website/src/lib/server/s3.ts +++ b/website/src/lib/server/s3.ts @@ -2,6 +2,7 @@ import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } fro 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'; +import { processImage } from './image.js'; const s3Client = new S3Client({ endpoint: PUBLIC_B2_ENDPOINT, @@ -47,7 +48,6 @@ export async function uploadProfilePicture( identifier: string, // Can be user ID or a unique ID from social provider body: Uint8Array, contentType: string, - contentLength?: number ): Promise { if (!contentType || !contentType.startsWith('image/')) { throw new Error('Invalid file type. Only images are allowed.'); @@ -58,17 +58,16 @@ export async function uploadProfilePicture( throw new Error('Unsupported image format. Only JPEG, PNG, GIF, and WebP are allowed.'); } - let fileExtension = contentType.split('/')[1]; - if (fileExtension === 'jpeg') fileExtension = 'jpg'; - - const key = `avatars/${identifier}.${fileExtension}`; + const processedImage = await processImage(Buffer.from(body)); + + const key = `avatars/${identifier}.webp`; const command = new PutObjectCommand({ Bucket: PUBLIC_B2_BUCKET, Key: key, - Body: body, - ContentType: contentType, - ...(contentLength && { ContentLength: contentLength }), + Body: processedImage.buffer, + ContentType: processedImage.contentType, + ContentLength: processedImage.size, }); await s3Client.send(command); @@ -79,7 +78,6 @@ export async function uploadCoinIcon( coinSymbol: string, body: Uint8Array, contentType: string, - contentLength?: number ): Promise { if (!contentType || !contentType.startsWith('image/')) { throw new Error('Invalid file type. Only images are allowed.'); @@ -90,17 +88,16 @@ export async function uploadCoinIcon( throw new Error('Unsupported image format. Only JPEG, PNG, GIF, and WebP are allowed.'); } - let fileExtension = contentType.split('/')[1]; - if (fileExtension === 'jpeg') fileExtension = 'jpg'; + const processedImage = await processImage(Buffer.from(body)); - const key = `coins/${coinSymbol.toLowerCase()}.${fileExtension}`; + const key = `coins/${coinSymbol.toLowerCase()}.webp`; const command = new PutObjectCommand({ Bucket: PUBLIC_B2_BUCKET, Key: key, - Body: body, - ContentType: contentType, - ...(contentLength && { ContentLength: contentLength }), + Body: processedImage.buffer, + ContentType: processedImage.contentType, + ContentLength: processedImage.size, }); await s3Client.send(command); diff --git a/website/src/routes/api/coin/create/+server.ts b/website/src/routes/api/coin/create/+server.ts index 00172b2..c89bee3 100644 --- a/website/src/routes/api/coin/create/+server.ts +++ b/website/src/routes/api/coin/create/+server.ts @@ -49,8 +49,7 @@ async function handleIconUpload(iconFile: File | null, symbol: string): Promise< return await uploadCoinIcon( symbol, new Uint8Array(arrayBuffer), - iconFile.type, - iconFile.size + iconFile.type ); } diff --git a/website/src/routes/api/settings/+server.ts b/website/src/routes/api/settings/+server.ts index 90bae35..ee757b8 100644 --- a/website/src/routes/api/settings/+server.ts +++ b/website/src/routes/api/settings/+server.ts @@ -91,8 +91,7 @@ export async function POST({ request }) { const key = await uploadProfilePicture( session.user.id, new Uint8Array(arrayBuffer), - avatarFile.type, - avatarFile.size + avatarFile.type ); updates.image = key; } catch (e) { From 1ab442045f2b054f4e41c7a4c42972af295ae2b5 Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Sat, 14 Jun 2025 19:16:39 +0600 Subject: [PATCH 3/7] fix p&l calculation to use total cost basis instead of avrg --- .../src/routes/api/portfolio/total/+server.ts | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/website/src/routes/api/portfolio/total/+server.ts b/website/src/routes/api/portfolio/total/+server.ts index bc77fd9..4b25be1 100644 --- a/website/src/routes/api/portfolio/total/+server.ts +++ b/website/src/routes/api/portfolio/total/+server.ts @@ -44,7 +44,20 @@ export async function GET({ request }) { const value = quantity * price; totalCoinValue += value; - // Calculate average purchase price from buy transactions + // Calculate total cost basis from buy transactions + const costBasisResult = await db.select({ + totalCostBasis: sql`COALESCE(SUM(${transaction.totalBaseCurrencyAmount}), 0)` + }) + .from(transaction) + .where( + and( + eq(transaction.userId, userId), + eq(transaction.coinId, holding.coinId), + eq(transaction.type, 'BUY') + ) + ); + + // Calculate average purchase price for reference const avgPriceResult = await db.select({ avgPrice: sql` CASE @@ -54,18 +67,20 @@ export async function GET({ request }) { END ` }) - .from(transaction) - .where( - and( - eq(transaction.userId, userId), - eq(transaction.coinId, holding.coinId), - eq(transaction.type, 'BUY') - ) - ); + .from(transaction) + .where( + and( + eq(transaction.userId, userId), + eq(transaction.coinId, holding.coinId), + eq(transaction.type, 'BUY') + ) + ); + const totalCostBasis = Number(costBasisResult[0]?.totalCostBasis || 0); const avgPurchasePrice = Number(avgPriceResult[0]?.avgPrice || 0); - const percentageChange = avgPurchasePrice > 0 - ? ((price - avgPurchasePrice) / avgPurchasePrice) * 100 + + const percentageChange = totalCostBasis > 0 + ? ((value - totalCostBasis) / totalCostBasis) * 100 : 0; return { @@ -76,7 +91,8 @@ export async function GET({ request }) { value, change24h: Number(holding.change24h), avgPurchasePrice, - percentageChange + percentageChange, + costBasis: totalCostBasis }; })); From 214c7cf3df1f74da208b249cc22d11d379514049 Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Sat, 14 Jun 2025 19:33:12 +0600 Subject: [PATCH 4/7] feat: "skip" hopium on low confidence, refund --- website/drizzle/0001_cuddly_dormammu.sql | 1 + website/drizzle/meta/0001_snapshot.json | 1720 +++++++++++++++++ website/drizzle/meta/_journal.json | 7 + website/src/lib/server/db/schema.ts | 1 + website/src/lib/server/job.ts | 87 +- .../routes/api/hopium/questions/+server.ts | 19 +- website/src/routes/hopium/+page.svelte | 7 +- website/src/routes/hopium/[id]/+page.svelte | 5 + 8 files changed, 1841 insertions(+), 6 deletions(-) create mode 100644 website/drizzle/0001_cuddly_dormammu.sql create mode 100644 website/drizzle/meta/0001_snapshot.json diff --git a/website/drizzle/0001_cuddly_dormammu.sql b/website/drizzle/0001_cuddly_dormammu.sql new file mode 100644 index 0000000..c1828c0 --- /dev/null +++ b/website/drizzle/0001_cuddly_dormammu.sql @@ -0,0 +1 @@ +CREATE INDEX IF NOT EXISTS "prediction_question_status_resolution_idx" ON "prediction_question" USING btree ("status","resolution_date"); \ No newline at end of file diff --git a/website/drizzle/meta/0001_snapshot.json b/website/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..ceac887 --- /dev/null +++ b/website/drizzle/meta/0001_snapshot.json @@ -0,0 +1,1720 @@ +{ + "id": "3eec8d9d-b3cd-4af8-8cde-73c38bd840e7", + "prevId": "41f7bba3-1d5d-41ba-83bb-ca129ace81f0", + "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.account_deletion_request": { + "name": "account_deletion_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "scheduled_deletion_at": { + "name": "scheduled_deletion_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_processed": { + "name": "is_processed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "account_deletion_request_user_id_idx": { + "name": "account_deletion_request_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_deletion_request_scheduled_deletion_idx": { + "name": "account_deletion_request_scheduled_deletion_idx", + "columns": [ + { + "expression": "scheduled_deletion_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_deletion_request_open_idx": { + "name": "account_deletion_request_open_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "is_processed = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_deletion_request_user_id_user_id_fk": { + "name": "account_deletion_request_user_id_user_id_fk", + "tableFrom": "account_deletion_request", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "account_deletion_request_user_id_unique": { + "name": "account_deletion_request_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "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(30, 8)", + "primaryKey": false, + "notNull": true + }, + "circulating_supply": { + "name": "circulating_supply", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true + }, + "current_price": { + "name": "current_price", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "market_cap": { + "name": "market_cap", + "type": "numeric(30, 2)", + "primaryKey": false, + "notNull": true + }, + "volume_24h": { + "name": "volume_24h", + "type": "numeric(30, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.00'" + }, + "change_24h": { + "name": "change_24h", + "type": "numeric(30, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.0000'" + }, + "pool_coin_amount": { + "name": "pool_coin_amount", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "pool_base_currency_amount": { + "name": "pool_base_currency_amount", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "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.comment": { + "name": "comment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "coin_id": { + "name": "coin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "likes_count": { + "name": "likes_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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_deleted": { + "name": "is_deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "comment_user_id_idx": { + "name": "comment_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "comment_coin_id_idx": { + "name": "comment_coin_id_idx", + "columns": [ + { + "expression": "coin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "comment_user_id_user_id_fk": { + "name": "comment_user_id_user_id_fk", + "tableFrom": "comment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "comment_coin_id_coin_id_fk": { + "name": "comment_coin_id_coin_id_fk", + "tableFrom": "comment", + "tableTo": "coin", + "columnsFrom": [ + "coin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.comment_like": { + "name": "comment_like", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "comment_like_user_id_user_id_fk": { + "name": "comment_like_user_id_user_id_fk", + "tableFrom": "comment_like", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "comment_like_comment_id_comment_id_fk": { + "name": "comment_like_comment_id_comment_id_fk", + "tableFrom": "comment_like", + "tableTo": "comment", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "comment_like_user_id_comment_id_pk": { + "name": "comment_like_user_id_comment_id_pk", + "columns": [ + "user_id", + "comment_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notification_user_id_idx": { + "name": "notification_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_type_idx": { + "name": "notification_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_is_read_idx": { + "name": "notification_is_read_idx", + "columns": [ + { + "expression": "is_read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_created_at_idx": { + "name": "notification_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_user_id_user_id_fk": { + "name": "notification_user_id_user_id_fk", + "tableFrom": "notification", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.prediction_bet": { + "name": "prediction_bet", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "question_id": { + "name": "question_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "actual_winnings": { + "name": "actual_winnings", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "prediction_bet_user_id_idx": { + "name": "prediction_bet_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_bet_question_id_idx": { + "name": "prediction_bet_question_id_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_bet_user_question_idx": { + "name": "prediction_bet_user_question_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_bet_created_at_idx": { + "name": "prediction_bet_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prediction_bet_user_id_user_id_fk": { + "name": "prediction_bet_user_id_user_id_fk", + "tableFrom": "prediction_bet", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "prediction_bet_question_id_prediction_question_id_fk": { + "name": "prediction_bet_question_id_prediction_question_id_fk", + "tableFrom": "prediction_bet", + "tableTo": "prediction_question", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.prediction_question": { + "name": "prediction_question", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "prediction_market_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "resolution_date": { + "name": "resolution_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ai_resolution": { + "name": "ai_resolution", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "total_yes_amount": { + "name": "total_yes_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "total_no_amount": { + "name": "total_no_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "requires_web_search": { + "name": "requires_web_search", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "validation_reason": { + "name": "validation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "prediction_question_creator_id_idx": { + "name": "prediction_question_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_question_status_idx": { + "name": "prediction_question_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_question_resolution_date_idx": { + "name": "prediction_question_resolution_date_idx", + "columns": [ + { + "expression": "resolution_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_question_status_resolution_idx": { + "name": "prediction_question_status_resolution_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resolution_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prediction_question_creator_id_user_id_fk": { + "name": "prediction_question_creator_id_user_id_fk", + "tableFrom": "prediction_question", + "tableTo": "user", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "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(20, 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.promo_code": { + "name": "promo_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_amount": { + "name": "reward_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "promo_code_created_by_user_id_fk": { + "name": "promo_code_created_by_user_id_fk", + "tableFrom": "promo_code", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "promo_code_code_unique": { + "name": "promo_code_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.promo_code_redemption": { + "name": "promo_code_redemption", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "promo_code_id": { + "name": "promo_code_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reward_amount": { + "name": "reward_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "promo_code_redemption_user_id_user_id_fk": { + "name": "promo_code_redemption_user_id_user_id_fk", + "tableFrom": "promo_code_redemption", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "promo_code_redemption_promo_code_id_promo_code_id_fk": { + "name": "promo_code_redemption_promo_code_id_promo_code_id_fk", + "tableFrom": "promo_code_redemption", + "tableTo": "promo_code", + "columnsFrom": [ + "promo_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "promo_code_redemption_user_id_promo_code_id_unique": { + "name": "promo_code_redemption_user_id_promo_code_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "promo_code_id" + ] + } + } + }, + "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": false + }, + "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(30, 8)", + "primaryKey": false, + "notNull": true + }, + "price_per_coin": { + "name": "price_per_coin", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "total_base_currency_amount": { + "name": "total_base_currency_amount", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "recipient_user_id": { + "name": "recipient_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sender_user_id": { + "name": "sender_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "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": "set null", + "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" + }, + "transaction_recipient_user_id_user_id_fk": { + "name": "transaction_recipient_user_id_user_id_fk", + "tableFrom": "transaction", + "tableTo": "user", + "columnsFrom": [ + "recipient_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "transaction_sender_user_id_user_id_fk": { + "name": "transaction_sender_user_id_user_id_fk", + "tableFrom": "transaction", + "tableTo": "user", + "columnsFrom": [ + "sender_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'100.00000000'" + }, + "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 + }, + "volume_master": { + "name": "volume_master", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0.70'" + }, + "volume_muted": { + "name": "volume_muted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_reward_claim": { + "name": "last_reward_claim", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_rewards_claimed": { + "name": "total_rewards_claimed", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "login_streak": { + "name": "login_streak", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "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(30, 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.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "HOPIUM", + "SYSTEM", + "TRANSFER", + "RUG_PULL" + ] + }, + "public.prediction_market_status": { + "name": "prediction_market_status", + "schema": "public", + "values": [ + "ACTIVE", + "RESOLVED", + "CANCELLED" + ] + }, + "public.transaction_type": { + "name": "transaction_type", + "schema": "public", + "values": [ + "BUY", + "SELL", + "TRANSFER_IN", + "TRANSFER_OUT" + ] + } + }, + "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 ceefa6b..bcd8ca7 100644 --- a/website/drizzle/meta/_journal.json +++ b/website/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1749654046953, "tag": "0000_crazy_bloodstrike", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1749907594739, + "tag": "0001_cuddly_dormammu", + "breakpoints": true } ] } \ 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 6fd02a6..341d512 100644 --- a/website/src/lib/server/db/schema.ts +++ b/website/src/lib/server/db/schema.ts @@ -190,6 +190,7 @@ export const predictionQuestion = pgTable("prediction_question", { creatorIdIdx: index("prediction_question_creator_id_idx").on(table.creatorId), statusIdx: index("prediction_question_status_idx").on(table.status), resolutionDateIdx: index("prediction_question_resolution_date_idx").on(table.resolutionDate), + statusResolutionIdx: index("prediction_question_status_resolution_idx").on(table.status, table.resolutionDate), }; }); diff --git a/website/src/lib/server/job.ts b/website/src/lib/server/job.ts index 528d15e..9f3a61a 100644 --- a/website/src/lib/server/job.ts +++ b/website/src/lib/server/job.ts @@ -38,7 +38,91 @@ export async function resolveExpiredQuestions() { ); if (resolution.confidence < 50) { - console.log(`Skipping question ${question.id} due to low confidence: ${resolution.confidence}`); + console.log(`Cancelling question ${question.id} due to low confidence: ${resolution.confidence}`); + + await db.transaction(async (tx) => { + // Mark question as cancelled + await tx + .update(predictionQuestion) + .set({ + status: 'CANCELLED', + resolvedAt: now, + }) + .where(eq(predictionQuestion.id, question.id)); + + // Get all bets for this question + const bets = await tx + .select({ + id: predictionBet.id, + userId: predictionBet.userId, + side: predictionBet.side, + amount: predictionBet.amount, + }) + .from(predictionBet) + .where(and( + eq(predictionBet.questionId, question.id), + isNull(predictionBet.settledAt) + )); + + const notificationsToCreate: Array<{ + userId: number; + amount: number; + }> = []; + + // Refund all bets + for (const bet of bets) { + const refundAmount = Number(bet.amount); + + // Mark bet as settled with full refund + await tx + .update(predictionBet) + .set({ + actualWinnings: refundAmount.toFixed(8), + settledAt: now, + }) + .where(eq(predictionBet.id, bet.id)); + + // Refund the user + if (bet.userId !== null) { + const [userData] = await tx + .select({ baseCurrencyBalance: user.baseCurrencyBalance }) + .from(user) + .where(eq(user.id, bet.userId)) + .limit(1); + + if (userData) { + const newBalance = Number(userData.baseCurrencyBalance) + refundAmount; + await tx + .update(user) + .set({ + baseCurrencyBalance: newBalance.toFixed(8), + updatedAt: now, + }) + .where(eq(user.id, bet.userId)); + } + + notificationsToCreate.push({ + userId: bet.userId, + amount: refundAmount + }); + } + } + + // Create refund notifications for all users who had bets + for (const notifData of notificationsToCreate) { + const { userId, amount } = notifData; + + const title = 'Prediction skipped 🥀'; + const message = `You received a full refund of ${formatValue(amount)} for "${question.question}". We recommend betting on more reliable predictions!`; + + await createNotification( + userId.toString(), + 'HOPIUM', + title, + message, + ); + } + }); continue; } @@ -139,7 +223,6 @@ export async function resolveExpiredQuestions() { } }); - console.log(`Successfully resolved question ${question.id}: ${resolution.resolution ? 'YES' : 'NO'} (confidence: ${resolution.confidence}%)`); } catch (error) { console.error(`Failed to resolve question ${question.id}:`, error); } diff --git a/website/src/routes/api/hopium/questions/+server.ts b/website/src/routes/api/hopium/questions/+server.ts index ab332f1..7af6619 100644 --- a/website/src/routes/api/hopium/questions/+server.ts +++ b/website/src/routes/api/hopium/questions/+server.ts @@ -2,7 +2,7 @@ import { auth } from '$lib/auth'; import { json } from '@sveltejs/kit'; import { db } from '$lib/server/db'; import { predictionQuestion, user, predictionBet } from '$lib/server/db/schema'; -import { eq, desc, and, sum, count } from 'drizzle-orm'; +import { eq, desc, and, sum, count, or } from 'drizzle-orm'; import type { RequestHandler } from './$types'; export const GET: RequestHandler = async ({ url, request }) => { @@ -24,9 +24,22 @@ export const GET: RequestHandler = async ({ url, request }) => { const userId = session?.user ? Number(session.user.id) : null; try { + let statusFilter; + + if (status === 'ACTIVE') { + statusFilter = eq(predictionQuestion.status, 'ACTIVE'); + } else if (status === 'RESOLVED') { + statusFilter = or( + eq(predictionQuestion.status, 'RESOLVED'), + eq(predictionQuestion.status, 'CANCELLED') + ); + } else { + statusFilter = undefined; + } + const conditions = []; - if (status !== 'ALL') { - conditions.push(eq(predictionQuestion.status, status as any)); + if (statusFilter) { + conditions.push(statusFilter); } const whereCondition = conditions.length > 0 ? and(...conditions) : undefined; diff --git a/website/src/routes/hopium/+page.svelte b/website/src/routes/hopium/+page.svelte index 0a11a61..c31e8e0 100644 --- a/website/src/routes/hopium/+page.svelte +++ b/website/src/routes/hopium/+page.svelte @@ -174,7 +174,7 @@

- Hopium[BETA] + Hopium

AI-powered prediction markets. Create questions and bet on outcomes. @@ -236,6 +236,11 @@ NO {/if} + {:else if question.status === 'CANCELLED'} + + + SKIP + {/if} diff --git a/website/src/routes/hopium/[id]/+page.svelte b/website/src/routes/hopium/[id]/+page.svelte index 7c97a04..a66e908 100644 --- a/website/src/routes/hopium/[id]/+page.svelte +++ b/website/src/routes/hopium/[id]/+page.svelte @@ -242,6 +242,11 @@ RESOLVED: NO {/if} + {:else if question.status === 'CANCELLED'} + + + SKIP + {/if}

From ec6426781d6227a8516a8dcda131a766d6c2fccb Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Sat, 14 Jun 2025 21:51:26 +0600 Subject: [PATCH 5/7] feat: prestige system --- package-lock.json | 6 + website/bun.lock | 8 +- website/drizzle/0002_small_micromacro.sql | 1 + website/drizzle/meta/0002_snapshot.json | 1727 +++++++++++++++++ website/drizzle/meta/_journal.json | 7 + .../src/lib/components/self/AppSidebar.svelte | 62 +- .../lib/components/self/ProfileBadges.svelte | 14 +- .../self/skeletons/PrestigeSkeleton.svelte | 150 ++ .../src/lib/components/ui/progress/index.ts | 7 + .../components/ui/progress/progress.svelte | 27 + website/src/lib/server/db/schema.ts | 3 +- website/src/lib/stores/user-data.ts | 2 + website/src/lib/types/user-profile.ts | 2 + website/src/lib/utils.ts | 48 +- website/src/routes/api/prestige/+server.ts | 171 ++ .../src/routes/api/user/[userId]/+server.ts | 1 + website/src/routes/prestige/+page.svelte | 477 +++++ 17 files changed, 2683 insertions(+), 30 deletions(-) create mode 100644 package-lock.json create mode 100644 website/drizzle/0002_small_micromacro.sql create mode 100644 website/drizzle/meta/0002_snapshot.json create mode 100644 website/src/lib/components/self/skeletons/PrestigeSkeleton.svelte create mode 100644 website/src/lib/components/ui/progress/index.ts create mode 100644 website/src/lib/components/ui/progress/progress.svelte create mode 100644 website/src/routes/api/prestige/+server.ts create mode 100644 website/src/routes/prestige/+page.svelte diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a7a55f4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "rugplay", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/website/bun.lock b/website/bun.lock index 23d1825..1a22afa 100644 --- a/website/bun.lock +++ b/website/bun.lock @@ -27,14 +27,14 @@ }, "devDependencies": { "@internationalized/date": "^3.8.1", - "@lucide/svelte": "^0.482.0", + "@lucide/svelte": "^0.515.0", "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/canvas-confetti": "^1.9.0", "@types/node": "^22.15.21", "autoprefixer": "^10.4.20", - "bits-ui": "^2.5.0", + "bits-ui": "^2.7.0", "clsx": "^2.1.1", "drizzle-kit": "^0.22.0", "prettier": "^3.3.2", @@ -218,7 +218,7 @@ "@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.11", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="], - "@lucide/svelte": ["@lucide/svelte@0.482.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-n2ycHU9cNcleRDwwpEHBJ6pYzVhHIaL3a+9dQa8kns9hB2g05bY+v2p2KP8v0pZwtNhYTHk/F2o2uZ1bVtQGhw=="], + "@lucide/svelte": ["@lucide/svelte@0.515.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA=="], "@noble/ciphers": ["@noble/ciphers@0.6.0", "", {}, "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ=="], @@ -516,7 +516,7 @@ "better-call": ["better-call@1.0.9", "", { "dependencies": { "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1", "uncrypto": "^0.1.3" } }, "sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g=="], - "bits-ui": ["bits-ui@2.5.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/dom": "^1.7.0", "css.escape": "^1.5.1", "esm-env": "^1.1.2", "runed": "^0.28.0", "svelte-toolbelt": "^0.9.1", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-PbjylA1UWd4A/c5AYqie/EVxQ1/8uugmJKLg9whLoBBHbfPEBGhK09dCPrahK9kA6DRHhMmij0XXIUGIfrmNow=="], + "bits-ui": ["bits-ui@2.7.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/dom": "^1.7.0", "css.escape": "^1.5.1", "esm-env": "^1.1.2", "runed": "^0.28.0", "svelte-toolbelt": "^0.9.1", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-2kjVSVBDt9SFnjFiA9vD74T5+UtAKWYpfOil9d4+7v0fSawvTNcMgOP05cD3FTJT/rarj78DjhaXdyTWIW6I/g=="], "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], diff --git a/website/drizzle/0002_small_micromacro.sql b/website/drizzle/0002_small_micromacro.sql new file mode 100644 index 0000000..5488a04 --- /dev/null +++ b/website/drizzle/0002_small_micromacro.sql @@ -0,0 +1 @@ +ALTER TABLE "user" ADD COLUMN "prestige_level" integer DEFAULT 0; \ No newline at end of file diff --git a/website/drizzle/meta/0002_snapshot.json b/website/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..9c38fdd --- /dev/null +++ b/website/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1727 @@ +{ + "id": "223d9abc-f0d3-4c71-9cda-8a069fc13205", + "prevId": "3eec8d9d-b3cd-4af8-8cde-73c38bd840e7", + "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.account_deletion_request": { + "name": "account_deletion_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "scheduled_deletion_at": { + "name": "scheduled_deletion_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_processed": { + "name": "is_processed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "account_deletion_request_user_id_idx": { + "name": "account_deletion_request_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_deletion_request_scheduled_deletion_idx": { + "name": "account_deletion_request_scheduled_deletion_idx", + "columns": [ + { + "expression": "scheduled_deletion_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_deletion_request_open_idx": { + "name": "account_deletion_request_open_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "is_processed = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_deletion_request_user_id_user_id_fk": { + "name": "account_deletion_request_user_id_user_id_fk", + "tableFrom": "account_deletion_request", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "account_deletion_request_user_id_unique": { + "name": "account_deletion_request_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "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(30, 8)", + "primaryKey": false, + "notNull": true + }, + "circulating_supply": { + "name": "circulating_supply", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true + }, + "current_price": { + "name": "current_price", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "market_cap": { + "name": "market_cap", + "type": "numeric(30, 2)", + "primaryKey": false, + "notNull": true + }, + "volume_24h": { + "name": "volume_24h", + "type": "numeric(30, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.00'" + }, + "change_24h": { + "name": "change_24h", + "type": "numeric(30, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.0000'" + }, + "pool_coin_amount": { + "name": "pool_coin_amount", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "pool_base_currency_amount": { + "name": "pool_base_currency_amount", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "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.comment": { + "name": "comment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "coin_id": { + "name": "coin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "likes_count": { + "name": "likes_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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_deleted": { + "name": "is_deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "comment_user_id_idx": { + "name": "comment_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "comment_coin_id_idx": { + "name": "comment_coin_id_idx", + "columns": [ + { + "expression": "coin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "comment_user_id_user_id_fk": { + "name": "comment_user_id_user_id_fk", + "tableFrom": "comment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "comment_coin_id_coin_id_fk": { + "name": "comment_coin_id_coin_id_fk", + "tableFrom": "comment", + "tableTo": "coin", + "columnsFrom": [ + "coin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.comment_like": { + "name": "comment_like", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "comment_like_user_id_user_id_fk": { + "name": "comment_like_user_id_user_id_fk", + "tableFrom": "comment_like", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "comment_like_comment_id_comment_id_fk": { + "name": "comment_like_comment_id_comment_id_fk", + "tableFrom": "comment_like", + "tableTo": "comment", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "comment_like_user_id_comment_id_pk": { + "name": "comment_like_user_id_comment_id_pk", + "columns": [ + "user_id", + "comment_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notification_user_id_idx": { + "name": "notification_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_type_idx": { + "name": "notification_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_is_read_idx": { + "name": "notification_is_read_idx", + "columns": [ + { + "expression": "is_read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_created_at_idx": { + "name": "notification_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_user_id_user_id_fk": { + "name": "notification_user_id_user_id_fk", + "tableFrom": "notification", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.prediction_bet": { + "name": "prediction_bet", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "question_id": { + "name": "question_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "actual_winnings": { + "name": "actual_winnings", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "prediction_bet_user_id_idx": { + "name": "prediction_bet_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_bet_question_id_idx": { + "name": "prediction_bet_question_id_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_bet_user_question_idx": { + "name": "prediction_bet_user_question_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_bet_created_at_idx": { + "name": "prediction_bet_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prediction_bet_user_id_user_id_fk": { + "name": "prediction_bet_user_id_user_id_fk", + "tableFrom": "prediction_bet", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "prediction_bet_question_id_prediction_question_id_fk": { + "name": "prediction_bet_question_id_prediction_question_id_fk", + "tableFrom": "prediction_bet", + "tableTo": "prediction_question", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.prediction_question": { + "name": "prediction_question", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "prediction_market_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "resolution_date": { + "name": "resolution_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ai_resolution": { + "name": "ai_resolution", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "total_yes_amount": { + "name": "total_yes_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "total_no_amount": { + "name": "total_no_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "requires_web_search": { + "name": "requires_web_search", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "validation_reason": { + "name": "validation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "prediction_question_creator_id_idx": { + "name": "prediction_question_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_question_status_idx": { + "name": "prediction_question_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_question_resolution_date_idx": { + "name": "prediction_question_resolution_date_idx", + "columns": [ + { + "expression": "resolution_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prediction_question_status_resolution_idx": { + "name": "prediction_question_status_resolution_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resolution_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prediction_question_creator_id_user_id_fk": { + "name": "prediction_question_creator_id_user_id_fk", + "tableFrom": "prediction_question", + "tableTo": "user", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "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(20, 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.promo_code": { + "name": "promo_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_amount": { + "name": "reward_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "promo_code_created_by_user_id_fk": { + "name": "promo_code_created_by_user_id_fk", + "tableFrom": "promo_code", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "promo_code_code_unique": { + "name": "promo_code_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.promo_code_redemption": { + "name": "promo_code_redemption", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "promo_code_id": { + "name": "promo_code_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reward_amount": { + "name": "reward_amount", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "promo_code_redemption_user_id_user_id_fk": { + "name": "promo_code_redemption_user_id_user_id_fk", + "tableFrom": "promo_code_redemption", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "promo_code_redemption_promo_code_id_promo_code_id_fk": { + "name": "promo_code_redemption_promo_code_id_promo_code_id_fk", + "tableFrom": "promo_code_redemption", + "tableTo": "promo_code", + "columnsFrom": [ + "promo_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "promo_code_redemption_user_id_promo_code_id_unique": { + "name": "promo_code_redemption_user_id_promo_code_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "promo_code_id" + ] + } + } + }, + "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": false + }, + "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(30, 8)", + "primaryKey": false, + "notNull": true + }, + "price_per_coin": { + "name": "price_per_coin", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true + }, + "total_base_currency_amount": { + "name": "total_base_currency_amount", + "type": "numeric(30, 8)", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "recipient_user_id": { + "name": "recipient_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sender_user_id": { + "name": "sender_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "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": "set null", + "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" + }, + "transaction_recipient_user_id_user_id_fk": { + "name": "transaction_recipient_user_id_user_id_fk", + "tableFrom": "transaction", + "tableTo": "user", + "columnsFrom": [ + "recipient_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "transaction_sender_user_id_user_id_fk": { + "name": "transaction_sender_user_id_user_id_fk", + "tableFrom": "transaction", + "tableTo": "user", + "columnsFrom": [ + "sender_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'100.00000000'" + }, + "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 + }, + "volume_master": { + "name": "volume_master", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0.70'" + }, + "volume_muted": { + "name": "volume_muted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_reward_claim": { + "name": "last_reward_claim", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_rewards_claimed": { + "name": "total_rewards_claimed", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000000'" + }, + "login_streak": { + "name": "login_streak", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "prestige_level": { + "name": "prestige_level", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "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(30, 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.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "HOPIUM", + "SYSTEM", + "TRANSFER", + "RUG_PULL" + ] + }, + "public.prediction_market_status": { + "name": "prediction_market_status", + "schema": "public", + "values": [ + "ACTIVE", + "RESOLVED", + "CANCELLED" + ] + }, + "public.transaction_type": { + "name": "transaction_type", + "schema": "public", + "values": [ + "BUY", + "SELL", + "TRANSFER_IN", + "TRANSFER_OUT" + ] + } + }, + "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 bcd8ca7..aede010 100644 --- a/website/drizzle/meta/_journal.json +++ b/website/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1749907594739, "tag": "0001_cuddly_dormammu", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1749916220202, + "tag": "0002_small_micromacro", + "breakpoints": true } ] } \ 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 d9917b4..432a87d 100644 --- a/website/src/lib/components/self/AppSidebar.svelte +++ b/website/src/lib/components/self/AppSidebar.svelte @@ -31,7 +31,8 @@ Hammer, BookOpen, Info, - Bell + Bell, + Crown } from 'lucide-svelte'; import { mode, setMode } from 'mode-watcher'; import type { HTMLAttributes } from 'svelte/elements'; @@ -85,7 +86,7 @@ function handleModeToggle() { setMode(mode.current === 'light' ? 'dark' : 'light'); - setOpenMobile(false); + // Remove setOpenMobile(false) to keep menu open } function formatCurrency(value: number): string { @@ -152,6 +153,11 @@ showUserManual = true; setOpenMobile(false); } + + function handlePrestigeClick() { + goto('/prestige'); + setOpenMobile(false); + } @@ -195,22 +201,6 @@ {/each} - - - - {#snippet child({ props }: { props: MenuButtonProps })} - - {/snippet} - - @@ -421,6 +411,8 @@ + + @@ -430,10 +422,16 @@ Settings - - - User Manual + + + Prestige + + + + + + { showPromoCode = true; @@ -443,10 +441,24 @@ Promo code + + + User Manual + + + {#if mode.current === 'light'} + + Dark Mode + {:else} + + Light Mode + {/if} + {#if $USER_DATA?.isAdmin} + {/if} + + + + - Terms of Service @@ -482,7 +497,10 @@ Privacy Policy + + + { signOut().then(() => { diff --git a/website/src/lib/components/self/ProfileBadges.svelte b/website/src/lib/components/self/ProfileBadges.svelte index fa041ea..fe5691c 100644 --- a/website/src/lib/components/self/ProfileBadges.svelte +++ b/website/src/lib/components/self/ProfileBadges.svelte @@ -1,7 +1,8 @@
{#if showId} {/if} + {#if prestigeName} + + {/if} {#if user.loginStreak && user.loginStreak > 1} - + {/if} {#if user.isAdmin} diff --git a/website/src/lib/components/self/skeletons/PrestigeSkeleton.svelte b/website/src/lib/components/self/skeletons/PrestigeSkeleton.svelte new file mode 100644 index 0000000..2293615 --- /dev/null +++ b/website/src/lib/components/self/skeletons/PrestigeSkeleton.svelte @@ -0,0 +1,150 @@ + + +
+ +
+ + + + How + + +
+ {#each Array(3) as _, i} +
+
+ {i + 1} +
+
+ + + +
+
+ {/each} +
+
+
+ + + + + + + Progress + + + + +
+
+
+ + +
+ +
+ + +
+ + + {#each Array(3) as _} + + + + + {/each} + +
+ + + +
+
+
+ + + + + +
+
+
+ + +
+ + + + Preview + + + +
+ +
+ + + + + +
+
+ + +
+ +
+
+
+ + +
+ +
+ + + + + +
+
+ + +
+ +
+
+
+
+
+ + + + + Levels + + + {#each Array(5) as _} +
+
+ + +
+ +
+ {/each} +
+
+
+
diff --git a/website/src/lib/components/ui/progress/index.ts b/website/src/lib/components/ui/progress/index.ts new file mode 100644 index 0000000..25eee61 --- /dev/null +++ b/website/src/lib/components/ui/progress/index.ts @@ -0,0 +1,7 @@ +import Root from "./progress.svelte"; + +export { + Root, + // + Root as Progress, +}; diff --git a/website/src/lib/components/ui/progress/progress.svelte b/website/src/lib/components/ui/progress/progress.svelte new file mode 100644 index 0000000..6833013 --- /dev/null +++ b/website/src/lib/components/ui/progress/progress.svelte @@ -0,0 +1,27 @@ + + + +
+
diff --git a/website/src/lib/server/db/schema.ts b/website/src/lib/server/db/schema.ts index 341d512..b0b0015 100644 --- a/website/src/lib/server/db/schema.ts +++ b/website/src/lib/server/db/schema.ts @@ -31,7 +31,8 @@ export const user = pgTable("user", { precision: 20, scale: 8, }).notNull().default("0.00000000"), - loginStreak: integer("login_streak").notNull().default(0) + loginStreak: integer("login_streak").notNull().default(0), + prestigeLevel: integer("prestige_level").default(0), }); export const session = pgTable("session", { diff --git a/website/src/lib/stores/user-data.ts b/website/src/lib/stores/user-data.ts index d22ceca..1b57922 100644 --- a/website/src/lib/stores/user-data.ts +++ b/website/src/lib/stores/user-data.ts @@ -16,6 +16,8 @@ export type User = { volumeMaster: number; volumeMuted: boolean; + + prestigeLevel: number; } | null; export const USER_DATA = writable(undefined); \ No newline at end of file diff --git a/website/src/lib/types/user-profile.ts b/website/src/lib/types/user-profile.ts index a021652..c773f38 100644 --- a/website/src/lib/types/user-profile.ts +++ b/website/src/lib/types/user-profile.ts @@ -9,6 +9,8 @@ export interface UserProfile { isAdmin: boolean; totalPortfolioValue: number; loginStreak: number; + + prestigeLevel: number | null; } export interface UserStats { diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index 4b786d6..2f131b6 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -335,4 +335,50 @@ export const formatMarketCap = formatValue; export function timeToLocal(originalTime: number): number { const d = new Date(originalTime * 1000); return Math.floor(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()) / 1000); -} \ No newline at end of file +} + +export const PRESTIGE_COSTS = { + 1: 100_000, + 2: 250_000, + 3: 1_000_000, + 4: 5_000_000, + 5: 25_000_000 +} as const; + +export const PRESTIGE_NAMES = { + 1: 'Prestige I', + 2: 'Prestige II', + 3: 'Prestige III', + 4: 'Prestige IV', + 5: 'Prestige V' +} as const; + +export const PRESTIGE_COLORS = { + 1: 'text-blue-500', + 2: 'text-purple-500', + 3: 'text-yellow-500', + 4: 'text-orange-500', + 5: 'text-red-500' +} as const; + +export function getPrestigeName(level: number): string | null { + if (level <= 0) return null; + const clampedLevel = Math.min(level, 5) as keyof typeof PRESTIGE_NAMES; + return PRESTIGE_NAMES[clampedLevel]; +} + +export function getPrestigeCost(level: number): number | null { + if (level <= 0) return null; + const clampedLevel = Math.min(level, 5) as keyof typeof PRESTIGE_COSTS; + return PRESTIGE_COSTS[clampedLevel]; +} + +export function getPrestigeColor(level: number): string { + if (level <= 0) return 'text-gray-500'; + const clampedLevel = Math.min(level, 5) as keyof typeof PRESTIGE_COLORS; + return PRESTIGE_COLORS[clampedLevel]; +} + +export function getMaxPrestigeLevel(): number { + return 5; +} diff --git a/website/src/routes/api/prestige/+server.ts b/website/src/routes/api/prestige/+server.ts new file mode 100644 index 0000000..5416fec --- /dev/null +++ b/website/src/routes/api/prestige/+server.ts @@ -0,0 +1,171 @@ +import { auth } from '$lib/auth'; +import { error, json } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { user, userPortfolio, transaction, notifications, coin } from '$lib/server/db/schema'; +import { eq, sql } from 'drizzle-orm'; +import type { RequestHandler } from './$types'; +import { formatValue, getPrestigeCost, getPrestigeName } from '$lib/utils'; + +export const POST: RequestHandler = async ({ request, locals }) => { + const session = await auth.api.getSession({ headers: request.headers }); + if (!session?.user) throw error(401, 'Not authenticated'); + + const userId = Number(session.user.id); + + return await db.transaction(async (tx) => { + const [userData] = await tx + .select({ + baseCurrencyBalance: user.baseCurrencyBalance, + prestigeLevel: user.prestigeLevel + }) + .from(user) + .where(eq(user.id, userId)) + .for('update') + .limit(1); + + if (!userData) throw error(404, 'User not found'); + + const currentPrestige = userData.prestigeLevel || 0; + const nextPrestige = currentPrestige + 1; + const prestigeCost = getPrestigeCost(nextPrestige); + const prestigeName = getPrestigeName(nextPrestige); + + if (!prestigeCost || !prestigeName) { + throw error(400, 'Maximum prestige level reached'); + } + + const holdings = await tx + .select({ + coinId: userPortfolio.coinId, + quantity: userPortfolio.quantity, + currentPrice: coin.currentPrice, + symbol: coin.symbol + }) + .from(userPortfolio) + .leftJoin(coin, eq(userPortfolio.coinId, coin.id)) + .where(eq(userPortfolio.userId, userId)); + + let warningMessage = ''; + let totalSaleValue = 0; + + if (holdings.length > 0) { + warningMessage = `All ${holdings.length} coin holdings have been sold at current market prices. `; + + for (const holding of holdings) { + const quantity = Number(holding.quantity); + const price = Number(holding.currentPrice); + const saleValue = quantity * price; + totalSaleValue += saleValue; + + await tx.insert(transaction).values({ + coinId: holding.coinId!, + type: 'SELL', + quantity: holding.quantity, + pricePerCoin: holding.currentPrice || '0', + totalBaseCurrencyAmount: saleValue.toString(), + timestamp: new Date() + }); + } + + await tx + .delete(userPortfolio) + .where(eq(userPortfolio.userId, userId)); + } + + const currentBalance = Number(userData.baseCurrencyBalance) + totalSaleValue; + if (currentBalance < prestigeCost) { + throw error(400, `Insufficient funds. Need ${formatValue(prestigeCost)}, have ${formatValue(currentBalance)}`); + } + + await tx + .update(user) + .set({ + baseCurrencyBalance: '100.00000000', + prestigeLevel: nextPrestige, + updatedAt: new Date() + }) + .where(eq(user.id, userId)); + + await tx.delete(userPortfolio).where(eq(userPortfolio.userId, userId)); + + await tx.insert(notifications).values({ + userId: userId, + type: 'SYSTEM', + title: `${prestigeName} Achieved!`, + message: `Congratulations! You have successfully reached ${prestigeName}. Your portfolio has been reset and you can now start fresh with your new prestige badge.`, + }); + + return json({ + success: true, + newPrestigeLevel: nextPrestige, + costPaid: prestigeCost, + coinsSold: holdings.length, + totalSaleValue, + message: `${warningMessage}Congratulations! You've reached Prestige ${nextPrestige}!` + }); + }); +}; + +export const GET: RequestHandler = async ({ 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 [userProfile] = await db + .select({ + id: user.id, + name: user.name, + username: user.username, + bio: user.bio, + image: user.image, + createdAt: user.createdAt, + baseCurrencyBalance: user.baseCurrencyBalance, + isAdmin: user.isAdmin, + loginStreak: user.loginStreak, + prestigeLevel: user.prestigeLevel + }) + .from(user) + .where(eq(user.id, userId)) + .limit(1); + + if (!userProfile) { + throw error(404, 'User not found'); + } + + const [portfolioStats] = await db + .select({ + holdingsCount: sql`COUNT(*)`, + holdingsValue: sql`COALESCE(SUM(CAST(${userPortfolio.quantity} AS NUMERIC) * CAST(${coin.currentPrice} AS NUMERIC)), 0)` + }) + .from(userPortfolio) + .leftJoin(coin, eq(userPortfolio.coinId, coin.id)) + .where(eq(userPortfolio.userId, userId)); + + const baseCurrencyBalance = Number(userProfile.baseCurrencyBalance); + const holdingsValue = Number(portfolioStats?.holdingsValue || 0); + const holdingsCount = Number(portfolioStats?.holdingsCount || 0); + const totalPortfolioValue = baseCurrencyBalance + holdingsValue; + + return json({ + profile: { + ...userProfile, + baseCurrencyBalance, + totalPortfolioValue, + prestigeLevel: userProfile.prestigeLevel || 0 + }, + stats: { + totalPortfolioValue, + baseCurrencyBalance, + holdingsValue, + holdingsCount, + coinsCreated: 0, + totalTransactions: 0, + totalBuyVolume: 0, + totalSellVolume: 0, + transactions24h: 0, + buyVolume24h: 0, + sellVolume24h: 0 + } + }); +}; diff --git a/website/src/routes/api/user/[userId]/+server.ts b/website/src/routes/api/user/[userId]/+server.ts index 46d79ae..4961963 100644 --- a/website/src/routes/api/user/[userId]/+server.ts +++ b/website/src/routes/api/user/[userId]/+server.ts @@ -25,6 +25,7 @@ export async function GET({ params }) { baseCurrencyBalance: true, isAdmin: true, loginStreak: true, + prestigeLevel: true, } }); diff --git a/website/src/routes/prestige/+page.svelte b/website/src/routes/prestige/+page.svelte new file mode 100644 index 0000000..378a662 --- /dev/null +++ b/website/src/routes/prestige/+page.svelte @@ -0,0 +1,477 @@ + + + + + + + + + + + + + Confirm + + + This action is permanent and cannot be undone. Please review the consequences carefully. + + + +
+ + + + You will lose: +
    +
  • Cash balance: {formatValue(currentBalance)}
  • + {#if holdingsValue > 0} +
  • All coin holdings worth {formatValue(holdingsValue)}
  • + {/if} +
  • Total portfolio value: {formatValue(totalValue)}
  • +
+ We will automatically sell all your coin holdings. +
+
+ +
+ + +
+
+ + + + + +
+
+ +
+
+
+
+ +

Prestige

+
+

Reset your progress to advance your trading status

+
+
+ + {#if loading} + + {:else if !userData} +
+
+
Sign in to prestige
+

You need an account to prestige

+ +
+
+ {:else} +
+ +
+ + + + How + + +
+
+
+ 1 +
+
+

Meet Requirements

+

+ Accumulate enough cash to afford the prestige cost +

+
+
+
+
+ 2 +
+
+

Reset Progress

+

+ All cash and holdings are erased, but history remains +

+
+
+
+
+ 3 +
+
+

Gain Status

+

+ Earn an exclusive prestige title and start fresh +

+
+
+
+
+
+ + {#if !hasMaxPrestige} + + + + + + Progress + + + + +
+
+
+ Progress to {prestigeName} + {progressPercentage.toFixed(1)}% +
+ +
+ + +
+ + + + + + + + + + + {#if !canAfford} + + + + + {/if} + +
Required: + {formatValue(prestigeCost || 0)} +
Your Cash: + {formatValue(currentBalance)} +
Still needed: + {formatValue(amountNeeded)} +
+
+
+ + {#if !canAfford} + + {:else} + + + Prestiging is permanent and cannot be undone! + + {/if} + + + +
+
+ {:else} + + + + +

You're a star!

+

+ You have reached the highest prestige level available. +

+
+
+ {/if} + + + {#if error} + + + + ❌ {error} + + + {/if} +
+ + +
+ + {#if userData} + + + Preview + + + +
+ +
+ + + {userData.name?.charAt(0) || '?'} + +
+
+

{userData.name}

+ +
+

@{userData.username}

+
+
+
+ + +
+ +
+ + + {userData.name?.charAt(0) || '?'} + +
+
+

{userData.name}

+ +
+

@{userData.username}

+
+
+
+
+
+ {/if} + + + + + Levels + + + {#each Object.entries(PRESTIGE_COSTS) as [level, cost]} + {@const levelNum = parseInt(level)} + {@const isCurrentNext = levelNum === nextPrestige && !hasMaxPrestige} + {@const isAchieved = levelNum <= currentPrestige} +
+
+ {#if isAchieved} + + {:else if isCurrentNext} + + {:else} +
+ {/if} + + {PRESTIGE_NAMES[levelNum as keyof typeof PRESTIGE_NAMES]} + +
+ {formatValue(cost)} +
+ {/each} +
+
+
+
+ {/if} +
+ + From 8c23c685659a1315e871c7da7ebaf6123d1ad944 Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Sun, 15 Jun 2025 10:01:36 +0300 Subject: [PATCH 6/7] fix p&l --- .../src/routes/api/portfolio/total/+server.ts | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/website/src/routes/api/portfolio/total/+server.ts b/website/src/routes/api/portfolio/total/+server.ts index 4b25be1..6b0cdb7 100644 --- a/website/src/routes/api/portfolio/total/+server.ts +++ b/website/src/routes/api/portfolio/total/+server.ts @@ -44,40 +44,49 @@ export async function GET({ request }) { const value = quantity * price; totalCoinValue += value; - // Calculate total cost basis from buy transactions - const costBasisResult = await db.select({ - totalCostBasis: sql`COALESCE(SUM(${transaction.totalBaseCurrencyAmount}), 0)` + const allTransactions = await db.select({ + type: transaction.type, + quantity: transaction.quantity, + totalBaseCurrencyAmount: transaction.totalBaseCurrencyAmount, + timestamp: transaction.timestamp }) .from(transaction) .where( and( eq(transaction.userId, userId), - eq(transaction.coinId, holding.coinId), - eq(transaction.type, 'BUY') + eq(transaction.coinId, holding.coinId) ) - ); + ) + .orderBy(transaction.timestamp); - // Calculate average purchase price for reference - const avgPriceResult = await db.select({ - avgPrice: sql` - CASE - WHEN SUM(${transaction.quantity}) > 0 - THEN SUM(${transaction.totalBaseCurrencyAmount}) / SUM(${transaction.quantity}) - ELSE 0 - END - ` - }) - .from(transaction) - .where( - and( - eq(transaction.userId, userId), - eq(transaction.coinId, holding.coinId), - eq(transaction.type, 'BUY') - ) - ); + // calculate cost basis + let remainingQuantity = quantity; + let totalCostBasis = 0; + let runningQuantity = 0; - const totalCostBasis = Number(costBasisResult[0]?.totalCostBasis || 0); - const avgPurchasePrice = Number(avgPriceResult[0]?.avgPrice || 0); + for (const tx of allTransactions) { + const txQuantity = Number(tx.quantity); + const txAmount = Number(tx.totalBaseCurrencyAmount); + + if (tx.type === 'BUY') { + runningQuantity += txQuantity; + + // if we still need to account for held coins + if (remainingQuantity > 0) { + const quantityToAttribute = Math.min(txQuantity, remainingQuantity); + const avgPrice = txAmount / txQuantity; + totalCostBasis += quantityToAttribute * avgPrice; + remainingQuantity -= quantityToAttribute; + } + } else if (tx.type === 'SELL') { + runningQuantity -= txQuantity; + } + + // if we accounted for all held coins, break + if (remainingQuantity <= 0) break; + } + + const avgPurchasePrice = quantity > 0 ? totalCostBasis / quantity : 0; const percentageChange = totalCostBasis > 0 ? ((value - totalCostBasis) / totalCostBasis) * 100 From 39991cb6c341ebb3c13860a6b213208af275c233 Mon Sep 17 00:00:00 2001 From: MD1125 <166238654+MD1125@users.noreply.github.com> Date: Sun, 15 Jun 2025 20:50:55 +0200 Subject: [PATCH 7/7] Added Dice Game, Fixed Mines & Balance Display on all Gambling Games MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This update includes the following changes and improvements: - ✅ **Mines Game** - Added safety guards to prevent the edge-case failures that happend before. - Replaced the mine amount selector input with `+` and `–` buttons based on the suggestion. - 🎲 **Dice Game** - Integrated a new Dice game based on [this CodePen implementation](https://codepen.io/oradler/pen/zxxdqKe). - Included a sound effect for dice rolls to enhance user interaction. ( No Copyright Issues there ) --- .../lib/components/self/games/Coinflip.svelte | 14 +- .../src/lib/components/self/games/Dice.svelte | 480 ++++++++++++++++++ .../lib/components/self/games/Mines.svelte | 73 ++- .../lib/components/self/games/Slots.svelte | 15 +- website/src/lib/server/games/mines.ts | 87 +++- website/src/routes/+page.svelte | 6 +- .../src/routes/api/gambling/dice/+server.ts | 87 ++++ .../api/gambling/mines/cashout/+server.ts | 5 +- .../api/gambling/mines/reveal/+server.ts | 54 +- .../api/gambling/mines/start/+server.ts | 17 +- website/src/routes/gambling/+page.svelte | 9 + website/static/sound/dice.mp3 | Bin 0 -> 44544 bytes 12 files changed, 780 insertions(+), 67 deletions(-) create mode 100644 website/src/lib/components/self/games/Dice.svelte create mode 100644 website/src/routes/api/gambling/dice/+server.ts create mode 100644 website/static/sound/dice.mp3 diff --git a/website/src/lib/components/self/games/Coinflip.svelte b/website/src/lib/components/self/games/Coinflip.svelte index daa940d..1b77200 100644 --- a/website/src/lib/components/self/games/Coinflip.svelte +++ b/website/src/lib/components/self/games/Coinflip.svelte @@ -314,8 +314,20 @@ } } - onMount(() => { + onMount(async () => { volumeSettings.load(); + + try { + const response = await fetch('/api/portfolio/summary'); + if (!response.ok) { + throw new Error('Failed to fetch portfolio summary'); + } + const data = await response.json(); + balance = data.baseCurrencyBalance; + onBalanceUpdate?.(data.baseCurrencyBalance); + } catch (error) { + console.error('Failed to fetch balance:', error); + } }); diff --git a/website/src/lib/components/self/games/Dice.svelte b/website/src/lib/components/self/games/Dice.svelte new file mode 100644 index 0000000..bf9d5cf --- /dev/null +++ b/website/src/lib/components/self/games/Dice.svelte @@ -0,0 +1,480 @@ + + + + + Dice + Choose a number and roll the dice to win 3x your bet! + + +
+
+
+

Balance

+

{formatValue(balance)}

+
+ +
+
+
+ {#each Array(6) as _, i} +
+
+ {#each Array(getDotsForFace(i + 1)) as _} +
+ {/each} +
+
+ {/each} +
+
+
+ +
+ {#if lastResult && !isRolling} +
+ {#if lastResult.won} +

WIN

+

+ Won {formatValue(lastResult.payout)} on {lastResult.result} +

+ {:else} +

LOSS

+

+ Lost {formatValue(lastResult.amountWagered)} on {lastResult.result} +

+ {/if} +
+ {/if} +
+
+ +
+
+
Choose Number
+
+ {#each Array(6) as _, i} + + {/each} +
+
+ +
+ + +

+ Max bet: {MAX_BET_AMOUNT.toLocaleString()} +

+
+ +
+
+ + + + +
+
+ + +
+
+
+
+ + diff --git a/website/src/lib/components/self/games/Mines.svelte b/website/src/lib/components/self/games/Mines.svelte index affd016..ab685da 100644 --- a/website/src/lib/components/self/games/Mines.svelte +++ b/website/src/lib/components/self/games/Mines.svelte @@ -131,6 +131,7 @@ } if (autoCashoutTimer >= AUTO_CASHOUT_TIME) { isAutoCashout = true; + clearInterval(autoCashoutInterval); cashOut(); } }, 100); @@ -227,6 +228,12 @@ hasRevealedTile = false; isAutoCashout = false; resetAutoCashoutTimer(); + + // Prevents the Tiles getting revealed when you Abort your bet. + if (!result.isAbort) { + revealedTiles = [...Array(TOTAL_TILES).keys()]; + minePositions = result.minePositions; + } } catch (error) { console.error('Cashout error:', error); toast.error('Failed to cash out', { @@ -276,8 +283,21 @@ } } - onMount(() => { + // Dynmaically fetch the correct balance. + onMount(async () => { volumeSettings.load(); + + try { + const response = await fetch('/api/portfolio/summary'); + if (!response.ok) { + throw new Error('Failed to fetch portfolio summary'); + } + const data = await response.json(); + balance = data.baseCurrencyBalance; + onBalanceUpdate?.(data.baseCurrencyBalance); + } catch (error) { + console.error('Failed to fetch balance:', error); + } }); onDestroy(() => { @@ -359,7 +379,7 @@ +
{mineCount}
+ +

Mines

+
+ +
@@ -611,4 +638,4 @@ height: 32px; object-fit: contain; } - + \ No newline at end of file diff --git a/website/src/lib/components/self/games/Slots.svelte b/website/src/lib/components/self/games/Slots.svelte index 6ed0c6c..d935979 100644 --- a/website/src/lib/components/self/games/Slots.svelte +++ b/website/src/lib/components/self/games/Slots.svelte @@ -211,8 +211,21 @@ } }); - onMount(() => { + // Dynmaically fetch the correct balance. + onMount(async () => { volumeSettings.load(); + + try { + const response = await fetch('/api/portfolio/summary'); + if (!response.ok) { + throw new Error('Failed to fetch portfolio summary'); + } + const data = await response.json(); + balance = data.baseCurrencyBalance; + onBalanceUpdate?.(data.baseCurrencyBalance); + } catch (error) { + console.error('Failed to fetch balance:', error); + } }); diff --git a/website/src/lib/server/games/mines.ts b/website/src/lib/server/games/mines.ts index 2c95f24..766e10d 100644 --- a/website/src/lib/server/games/mines.ts +++ b/website/src/lib/server/games/mines.ts @@ -19,17 +19,13 @@ interface MinesSession { export const activeGames = new Map(); -// Clean up old games every minute. +// Clean up old games every minute. (5 Minute system) setInterval(async () => { const now = Date.now(); for (const [token, game] of activeGames.entries()) { - // Delete games older than 5 minutes that are still there for some reason. if (now - game.lastActivity > 5 * 60 * 1000) { - // If no tiles were revealed, refund the bet if (game.revealedTiles.length === 0) { - try { - console.log(`Processing refund for inactive Mines game ${token} (User: ${game.userId}, Bet: ${game.betAmount})`); - + try { const [userData] = await db .select({ baseCurrencyBalance: user.baseCurrencyBalance }) .from(user) @@ -48,29 +44,74 @@ setInterval(async () => { }) .where(eq(user.id, game.userId)); - console.log(`Successfully refunded ${game.betAmount} to user ${game.userId}. New balance: ${newBalance}`); } catch (error) { console.error(`Failed to refund inactive game ${token}:`, error); } - } else { - console.log(`Cleaning up inactive game ${token} (User: ${game.userId}) - No refund needed as tiles were revealed`); } activeGames.delete(token); } } }, 60000); +setInterval(async () => { + const now = Date.now(); + for (const [token, game] of activeGames.entries()) { + if (game.status === 'active' && game.revealedTiles.length > 0 && now - game.lastActivity > 20000) { + try { + const [userData] = await db + .select({ baseCurrencyBalance: user.baseCurrencyBalance }) + .from(user) + .where(eq(user.id, game.userId)) + .for('update') + .limit(1); + + const currentBalance = Number(userData.baseCurrencyBalance); + const payout = game.betAmount * game.currentMultiplier; + const roundedPayout = Math.round(payout * 100000000) / 100000000; + const newBalance = Math.round((currentBalance + roundedPayout) * 100000000) / 100000000; + + await db + .update(user) + .set({ + baseCurrencyBalance: newBalance.toFixed(8), + updatedAt: new Date() + }) + .where(eq(user.id, game.userId)); + + activeGames.delete(token); + } catch (error) { + console.error(`Failed to auto cashout game ${token}:`, error); + } + } + } +}, 15000); + // Rig the game... -const getMaxPayout = (bet: number, picks: number): number => { - const absoluteCap = 5_000_000; // never pay above this. Yeah, its rigged. Live with that :) - const baseCap = 1.4; // 1.4x min multiplier, increase to, well, increase payouts - const growthRate = 0.45; // cap curve sensitivity - - // Cap increases with number of successful reveals - const effectiveMultiplierCap = baseCap + Math.pow(picks, growthRate); - const payoutCap = bet * effectiveMultiplierCap; - - return Math.min(payoutCap, absoluteCap); +const getMaxPayout = (bet: number, picks: number, mines: number): number => { + const MAX_PAYOUT = 2_000_000; // Maximum payout cap of 2 million to not make linker too rich + const HIGH_BET_THRESHOLD = 50_000; + + const mineFactor = 1 + (mines / 25); + const baseMultiplier = (1.4 + Math.pow(picks, 0.45)) * mineFactor; + + // For high bets, we stop linker from getting richer ¯\_(ツ)_/¯ + if (bet > HIGH_BET_THRESHOLD) { + const betRatio = Math.pow(Math.min(1, (bet - HIGH_BET_THRESHOLD) / (MAX_PAYOUT - HIGH_BET_THRESHOLD)), 1); + + // Direct cap on multiplier for high bets + const maxAllowedMultiplier = 1.05 + (picks * 0.1); + const highBetMultiplier = Math.min(baseMultiplier, maxAllowedMultiplier) * (1 - (bet / MAX_PAYOUT) * 0.9); + const betSizeFactor = Math.max(0.1, 1 - (bet / MAX_PAYOUT) * 0.9); + const minMultiplier = (1.1 + (picks * 0.15 * betSizeFactor)) * mineFactor; + + const reducedMultiplier = highBetMultiplier - ((highBetMultiplier - minMultiplier) * betRatio); + const payout = Math.min(bet * reducedMultiplier, MAX_PAYOUT); + + return payout; + } + + const payout = Math.min(bet * baseMultiplier, MAX_PAYOUT); + return payout; }; @@ -78,6 +119,7 @@ export function calculateMultiplier(picks: number, mines: number, betAmount: num const TOTAL_TILES = 25; const HOUSE_EDGE = 0.05; + // Calculate probability of winning based on picks and mines let probability = 1; for (let i = 0; i < picks; i++) { probability *= (TOTAL_TILES - mines - i) / (TOTAL_TILES - i); @@ -85,14 +127,15 @@ export function calculateMultiplier(picks: number, mines: number, betAmount: num if (probability <= 0) return 1.0; + // Calculate fair multiplier based on probability and house edge const fairMultiplier = (1 / probability) * (1 - HOUSE_EDGE); + const rawPayout = fairMultiplier * betAmount; - - const maxPayout = getMaxPayout(betAmount, picks); + const maxPayout = getMaxPayout(betAmount, picks, mines); const cappedPayout = Math.min(rawPayout, maxPayout); const effectiveMultiplier = cappedPayout / betAmount; - return Math.max(1.0, effectiveMultiplier); + return Math.max(1.0, Number(effectiveMultiplier.toFixed(2))); } diff --git a/website/src/routes/+page.svelte b/website/src/routes/+page.svelte index e51493d..0fee172 100644 --- a/website/src/routes/+page.svelte +++ b/website/src/routes/+page.svelte @@ -95,11 +95,7 @@ class="text-primary underline hover:cursor-pointer" onclick={() => (shouldSignIn = !shouldSignIn)}>sign in - or{' '} - to play. + to play. {/if}

diff --git a/website/src/routes/api/gambling/dice/+server.ts b/website/src/routes/api/gambling/dice/+server.ts new file mode 100644 index 0000000..d023cf6 --- /dev/null +++ b/website/src/routes/api/gambling/dice/+server.ts @@ -0,0 +1,87 @@ +import { auth } from '$lib/auth'; +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 { randomBytes } from 'crypto'; +import type { RequestHandler } from './$types'; + +interface DiceRequest { + selectedNumber: number; + amount: number; +} + +export const POST: RequestHandler = async ({ request }) => { + const session = await auth.api.getSession({ + headers: request.headers + }); + + if (!session?.user) { + throw error(401, 'Not authenticated'); + } + + try { + const { selectedNumber, amount }: DiceRequest = await request.json(); + + if (!selectedNumber || selectedNumber < 1 || selectedNumber > 6 || !Number.isInteger(selectedNumber)) { + return json({ error: 'Invalid number selection' }, { status: 400 }); + } + + if (!amount || amount <= 0 || !Number.isFinite(amount)) { + return json({ error: 'Invalid bet amount' }, { status: 400 }); + } + + if (amount > 1000000) { + return json({ error: 'Bet amount too large' }, { status: 400 }); + } + + const userId = Number(session.user.id); + + const result = await db.transaction(async (tx) => { + const [userData] = await tx + .select({ baseCurrencyBalance: user.baseCurrencyBalance }) + .from(user) + .where(eq(user.id, userId)) + .for('update') + .limit(1); + + const currentBalance = Number(userData.baseCurrencyBalance); + + const roundedAmount = Math.round(amount * 100000000) / 100000000; + const roundedBalance = Math.round(currentBalance * 100000000) / 100000000; + + if (roundedAmount > roundedBalance) { + throw new Error(`Insufficient funds. You need *${roundedAmount.toFixed(2)} but only have *${roundedBalance.toFixed(2)}`); + } + + const gameResult = Math.floor(randomBytes(1)[0] / 42.67) + 1; // This gives us a number between 1-6 + const won = gameResult === selectedNumber; + + const multiplier = 3; + const payout = won ? roundedAmount * multiplier : 0; + const newBalance = roundedBalance - roundedAmount + payout; + + await tx + .update(user) + .set({ + baseCurrencyBalance: newBalance.toFixed(8), + updatedAt: new Date() + }) + .where(eq(user.id, userId)); + + return { + won, + result: gameResult, + newBalance, + payout, + amountWagered: roundedAmount + }; + }); + + return json(result); + } catch (e) { + console.error('Dice API error:', e); + const errorMessage = e instanceof Error ? e.message : 'Internal server error'; + return json({ error: errorMessage }, { status: 400 }); + } +}; diff --git a/website/src/routes/api/gambling/mines/cashout/+server.ts b/website/src/routes/api/gambling/mines/cashout/+server.ts index a91bb68..773e9ad 100644 --- a/website/src/routes/api/gambling/mines/cashout/+server.ts +++ b/website/src/routes/api/gambling/mines/cashout/+server.ts @@ -47,6 +47,7 @@ export const POST: RequestHandler = async ({ request }) => { newBalance = Math.round((currentBalance + roundedPayout) * 100000000) / 100000000; } + await tx .update(user) .set({ @@ -55,13 +56,15 @@ export const POST: RequestHandler = async ({ request }) => { }) .where(eq(user.id, userId)); + activeGames.delete(sessionToken); return { newBalance, payout, amountWagered: game.betAmount, - isAbort: game.revealedTiles.length === 0 + isAbort: game.revealedTiles.length === 0, + minePositions: game.minePositions }; }); diff --git a/website/src/routes/api/gambling/mines/reveal/+server.ts b/website/src/routes/api/gambling/mines/reveal/+server.ts index 75f95c7..6a711ec 100644 --- a/website/src/routes/api/gambling/mines/reveal/+server.ts +++ b/website/src/routes/api/gambling/mines/reveal/+server.ts @@ -27,35 +27,46 @@ export const POST: RequestHandler = async ({ request }) => { return json({ error: 'Tile already revealed' }, { status: 400 }); } - // Update last activity time game.lastActivity = Date.now(); - // Check if hit mine if (game.minePositions.includes(tileIndex)) { game.status = 'lost'; const minePositions = game.minePositions; - // Fetch user balance to return after loss const userId = Number(session.user.id); const [userData] = await db .select({ baseCurrencyBalance: user.baseCurrencyBalance }) .from(user) .where(eq(user.id, userId)) + .for('update') .limit(1); + + const currentBalance = Number(userData.baseCurrencyBalance); + + + await db + .update(user) + .set({ + baseCurrencyBalance: currentBalance.toFixed(8), + updatedAt: new Date() + }) + .where(eq(user.id, userId)); activeGames.delete(sessionToken); + return json({ hitMine: true, minePositions, - newBalance: Number(userData.baseCurrencyBalance), - status: 'lost' + newBalance: currentBalance, + status: 'lost', + amountWagered: game.betAmount }); } - // Safe tile + // Safe tile (Yipeee) game.revealedTiles.push(tileIndex); game.currentMultiplier = calculateMultiplier( game.revealedTiles.length, @@ -63,9 +74,38 @@ export const POST: RequestHandler = async ({ request }) => { game.betAmount ); - // Check if all safe tiles are revealed. Crazy when you get this :) if (game.revealedTiles.length === 25 - game.mineCount) { game.status = 'won'; + const userId = Number(session.user.id); + const [userData] = await db + .select({ baseCurrencyBalance: user.baseCurrencyBalance }) + .from(user) + .where(eq(user.id, userId)) + .for('update') + .limit(1); + + const currentBalance = Number(userData.baseCurrencyBalance); + const payout = game.betAmount * game.currentMultiplier; + const roundedPayout = Math.round(payout * 100000000) / 100000000; + const newBalance = Math.round((currentBalance + roundedPayout) * 100000000) / 100000000; + + await db + .update(user) + .set({ + baseCurrencyBalance: newBalance.toFixed(8), + updatedAt: new Date() + }) + .where(eq(user.id, userId)); + + activeGames.delete(sessionToken); + + return json({ + hitMine: false, + currentMultiplier: game.currentMultiplier, + status: 'won', + newBalance, + payout + }); } return json({ diff --git a/website/src/routes/api/gambling/mines/start/+server.ts b/website/src/routes/api/gambling/mines/start/+server.ts index 34e25d9..cff9ab4 100644 --- a/website/src/routes/api/gambling/mines/start/+server.ts +++ b/website/src/routes/api/gambling/mines/start/+server.ts @@ -43,6 +43,7 @@ export const POST: RequestHandler = async ({ request }) => { throw new Error(`Insufficient funds. You need *${roundedAmount.toFixed(2)} but only have *${roundedBalance.toFixed(2)}`); } + // Generate mine positions const positions = new Set(); while (positions.size < mineCount) { @@ -52,11 +53,8 @@ export const POST: RequestHandler = async ({ request }) => { for (let i = 0; i < 25; i++) { if (!positions.has(i)) safePositions.push(i); } - console.log(positions) - console.log('Safe positions:', safePositions); - - // transaction token for authentication + // transaction token for authentication stuff const randomBytes = new Uint8Array(8); crypto.getRandomValues(randomBytes); const sessionToken = Array.from(randomBytes) @@ -64,6 +62,7 @@ export const POST: RequestHandler = async ({ request }) => { .join(''); const now = Date.now(); + const newBalance = roundedBalance - roundedAmount; // Create session activeGames.set(sessionToken, { @@ -79,16 +78,20 @@ export const POST: RequestHandler = async ({ request }) => { userId }); - // Hold bet amount on server to prevent the user from like sending it to another account and farming money without a risk + // Update user balance await tx .update(user) .set({ - baseCurrencyBalance: (roundedBalance - roundedAmount).toFixed(8), + baseCurrencyBalance: newBalance.toFixed(8), updatedAt: new Date() }) .where(eq(user.id, userId)); - return { sessionToken }; + + return { + sessionToken, + newBalance + }; }); return json(result); diff --git a/website/src/routes/gambling/+page.svelte b/website/src/routes/gambling/+page.svelte index 27b7d0c..996aeb4 100644 --- a/website/src/routes/gambling/+page.svelte +++ b/website/src/routes/gambling/+page.svelte @@ -9,6 +9,7 @@ import SignInConfirmDialog from '$lib/components/self/SignInConfirmDialog.svelte'; import { Button } from '$lib/components/ui/button'; import SEO from '$lib/components/self/SEO.svelte'; + import Dice from '$lib/components/self/games/Dice.svelte' let shouldSignIn = $state(false); let balance = $state(0); @@ -78,6 +79,12 @@ > Mines +
@@ -87,6 +94,8 @@ {:else if activeGame === 'mines'} + {:else if activeGame === 'dice'} + {/if} {/if} diff --git a/website/static/sound/dice.mp3 b/website/static/sound/dice.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e73d79d6d3925eb2562926cd4644a3892a4883a5 GIT binary patch literal 44544 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