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} +
+ +