diff --git a/website/drizzle/0011_broken_risque.sql b/website/drizzle/0011_broken_risque.sql new file mode 100644 index 0000000..849a159 --- /dev/null +++ b/website/drizzle/0011_broken_risque.sql @@ -0,0 +1,2 @@ +ALTER TABLE "user" ADD COLUMN "volume_master" numeric(3, 2) DEFAULT '0.70' NOT NULL;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "volume_muted" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/website/drizzle/meta/0011_snapshot.json b/website/drizzle/meta/0011_snapshot.json new file mode 100644 index 0000000..4e3f5f6 --- /dev/null +++ b/website/drizzle/meta/0011_snapshot.json @@ -0,0 +1,1402 @@ +{ + "id": "de0863ba-af00-4bfe-a767-cc1a91275b10", + "prevId": "79e709ad-6f96-485e-9c1b-72beffa4b373", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.coin": { + "name": "coin", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "symbol": { + "name": "symbol", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "initial_supply": { + "name": "initial_supply", + "type": "numeric(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(10, 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": true + }, + "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": "cascade", + "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.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": true + }, + "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": "cascade", + "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": true + }, + "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": {} + } + }, + "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": "cascade", + "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": "no action", + "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": true + }, + "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": "no action", + "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": true + }, + "coin_id": { + "name": "coin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "transaction_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(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()" + } + }, + "indexes": {}, + "foreignKeys": { + "transaction_user_id_user_id_fk": { + "name": "transaction_user_id_user_id_fk", + "tableFrom": "transaction", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transaction_coin_id_coin_id_fk": { + "name": "transaction_coin_id_coin_id_fk", + "tableFrom": "transaction", + "tableTo": "coin", + "columnsFrom": [ + "coin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_banned": { + "name": "is_banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_currency_balance": { + "name": "base_currency_balance", + "type": "numeric(20, 8)", + "primaryKey": false, + "notNull": true, + "default": "'10000.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.prediction_market_status": { + "name": "prediction_market_status", + "schema": "public", + "values": [ + "ACTIVE", + "RESOLVED", + "CANCELLED" + ] + }, + "public.transaction_type": { + "name": "transaction_type", + "schema": "public", + "values": [ + "BUY", + "SELL" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/website/drizzle/meta/_journal.json b/website/drizzle/meta/_journal.json index 96d7076..18d9623 100644 --- a/website/drizzle/meta/_journal.json +++ b/website/drizzle/meta/_journal.json @@ -78,6 +78,13 @@ "when": 1748439588289, "tag": "0010_silent_shiva", "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1748528211995, + "tag": "0011_broken_risque", + "breakpoints": true } ] } \ No newline at end of file diff --git a/website/src/lib/auth.ts b/website/src/lib/auth.ts index 1f66919..cd0b7d0 100644 --- a/website/src/lib/auth.ts +++ b/website/src/lib/auth.ts @@ -64,6 +64,8 @@ export const auth = betterAuth({ banReason: { type: "string", required: false, input: false }, baseCurrencyBalance: { type: "string", required: false, input: false }, bio: { type: "string", required: false }, + volumeMaster: { type: "string", required: false, input: false }, + volumeMuted: { type: "boolean", required: false, input: false }, } }, session: { diff --git a/website/src/lib/components/self/games/Coinflip.svelte b/website/src/lib/components/self/games/Coinflip.svelte index 33d0087..daa940d 100644 --- a/website/src/lib/components/self/games/Coinflip.svelte +++ b/website/src/lib/components/self/games/Coinflip.svelte @@ -162,6 +162,8 @@ import confetti from 'canvas-confetti'; import { toast } from 'svelte-sonner'; import { formatValue, playSound, showConfetti } from '$lib/utils'; + import { volumeSettings } from '$lib/stores/volume-settings'; + import { onMount } from 'svelte'; interface CoinflipResult { won: boolean; @@ -311,6 +313,10 @@ activeSoundTimeouts = []; } } + + onMount(() => { + volumeSettings.load(); + }); diff --git a/website/src/lib/components/self/games/Slots.svelte b/website/src/lib/components/self/games/Slots.svelte index 75da8bf..6ed0c6c 100644 --- a/website/src/lib/components/self/games/Slots.svelte +++ b/website/src/lib/components/self/games/Slots.svelte @@ -11,6 +11,8 @@ import confetti from 'canvas-confetti'; import { toast } from 'svelte-sonner'; import { formatValue, playSound, showConfetti, showSchoolPrideCannons } from '$lib/utils'; + import { volumeSettings } from '$lib/stores/volume-settings'; + import { onMount } from 'svelte'; interface SlotsResult { won: boolean; @@ -208,6 +210,10 @@ } } }); + + onMount(() => { + volumeSettings.load(); + }); diff --git a/website/src/lib/components/ui/slider/index.ts b/website/src/lib/components/ui/slider/index.ts new file mode 100644 index 0000000..820f209 --- /dev/null +++ b/website/src/lib/components/ui/slider/index.ts @@ -0,0 +1,7 @@ +import Root from "./slider.svelte"; + +export { + Root, + // + Root as Slider, +}; diff --git a/website/src/lib/components/ui/slider/slider.svelte b/website/src/lib/components/ui/slider/slider.svelte new file mode 100644 index 0000000..b06e110 --- /dev/null +++ b/website/src/lib/components/ui/slider/slider.svelte @@ -0,0 +1,52 @@ + + + + + {#snippet children({ thumbs })} + + + + {#each thumbs as thumb (thumb)} + + {/each} + {/snippet} + diff --git a/website/src/lib/server/db/schema.ts b/website/src/lib/server/db/schema.ts index 96616b4..7242036 100644 --- a/website/src/lib/server/db/schema.ts +++ b/website/src/lib/server/db/schema.ts @@ -21,6 +21,9 @@ export const user = pgTable("user", { }).notNull().default("10000.00000000"), // 10,000 *BUSS bio: varchar("bio", { length: 160 }).default("Hello am 48 year old man from somalia. Sorry for my bed england. I selled my wife for internet connection for play “conter stirk”"), username: varchar("username", { length: 30 }).notNull().unique(), + + volumeMaster: decimal("volume_master", { precision: 3, scale: 2 }).notNull().default("0.70"), + volumeMuted: boolean("volume_muted").notNull().default(false), lastRewardClaim: timestamp("last_reward_claim", { withTimezone: true }), totalRewardsClaimed: decimal("total_rewards_claimed", { diff --git a/website/src/lib/stores/user-data.ts b/website/src/lib/stores/user-data.ts index 2a4b92d..d22ceca 100644 --- a/website/src/lib/stores/user-data.ts +++ b/website/src/lib/stores/user-data.ts @@ -13,6 +13,9 @@ export type User = { baseCurrencyBalance: number; bio: string; + + volumeMaster: number; + volumeMuted: boolean; } | null; export const USER_DATA = writable(undefined); \ No newline at end of file diff --git a/website/src/lib/stores/volume-settings.ts b/website/src/lib/stores/volume-settings.ts new file mode 100644 index 0000000..09e2d88 --- /dev/null +++ b/website/src/lib/stores/volume-settings.ts @@ -0,0 +1,53 @@ +import { writable } from 'svelte/store'; +import { browser } from '$app/environment'; + +export interface VolumeSettings { + master: number; + muted: boolean; +} + +const defaultSettings: VolumeSettings = { + master: 0.7, + muted: false +}; + +function createVolumeSettings() { + const { subscribe, set, update } = writable(defaultSettings); + + return { + subscribe, + load: () => { + if (browser) { + const stored = localStorage.getItem('volume-settings'); + if (stored) { + try { + const settings = JSON.parse(stored); + set({ ...defaultSettings, ...settings }); + } catch (e) { + console.error('Failed to parse volume settings:', e); + } + } + } + }, + setMaster: (value: number) => { + update(settings => { + const newSettings = { ...settings, master: Math.max(0, Math.min(1, value)) }; + if (browser) { + localStorage.setItem('volume-settings', JSON.stringify(newSettings)); + } + return newSettings; + }); + }, + setMuted: (value: boolean) => { + update(settings => { + const newSettings = { ...settings, muted: value }; + if (browser) { + localStorage.setItem('volume-settings', JSON.stringify(newSettings)); + } + return newSettings; + }); + } + }; +} + +export const volumeSettings = createVolumeSettings(); diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index 6776025..0da9ac9 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -1,6 +1,8 @@ import { PUBLIC_B2_BUCKET, PUBLIC_B2_ENDPOINT } from "$env/static/public"; import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; +import { volumeSettings } from '$lib/stores/volume-settings'; +import { get } from 'svelte/store'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -235,16 +237,13 @@ export function getTimeframeInSeconds(timeframe: string): number { let availableSounds = [1, 2, 3, 4, 5, 6, 7]; export function playRandomFireworkSound() { - // If no sounds available, reset the array if (availableSounds.length === 0) { availableSounds = [1, 2, 3, 4, 5, 6, 7]; } - // Pick a random sound from available ones const randomIndex = Math.floor(Math.random() * availableSounds.length); const soundNumber = availableSounds[randomIndex]; - // Remove the sound from available array to prevent repetition availableSounds = availableSounds.filter((_, index) => index !== randomIndex); playSound(`firework${soundNumber}`); @@ -252,8 +251,14 @@ export function playRandomFireworkSound() { export function playSound(sound: string) { try { - const audio = new Audio(`sound/${sound}.mp3`); - audio.volume = 0.3; // TODO: volume control + const settings = get(volumeSettings); + + if (settings.muted) { + return; + } + + const audio = new Audio(`/sound/${sound}.mp3`); + audio.volume = Math.max(0, Math.min(1, settings.master)); audio.play().catch(console.error); } catch (error) { console.error('Error playing sound:', error); diff --git a/website/src/routes/+layout.svelte b/website/src/routes/+layout.svelte index b7b29f1..18d60d3 100644 --- a/website/src/routes/+layout.svelte +++ b/website/src/routes/+layout.svelte @@ -7,7 +7,7 @@ import AppSidebar from '$lib/components/self/AppSidebar.svelte'; import { USER_DATA } from '$lib/stores/user-data'; - import { onMount } from 'svelte'; + import { onMount, onDestroy } from 'svelte'; // onDestroy is already imported import { invalidateAll } from '$app/navigation'; import { ModeWatcher } from 'mode-watcher'; import { page } from '$app/state'; @@ -18,12 +18,10 @@ children: any; }>(); + USER_DATA.set(data?.userSession ?? null); + $effect(() => { - if (data?.userSession) { - USER_DATA.set(data.userSession); - } else { - USER_DATA.set(null); - } + USER_DATA.set(data?.userSession ?? null); }); onMount(() => { @@ -84,7 +82,7 @@ const titleMap: Record = { '/': 'Home', '/market': 'Market', - '/portfolio': 'Portfolio', + '/portfolio': 'Portfolio', '/leaderboard': 'Leaderboard', '/coin/create': 'Create Coin', '/settings': 'Settings', diff --git a/website/src/routes/api/settings/volume/+server.ts b/website/src/routes/api/settings/volume/+server.ts new file mode 100644 index 0000000..da84eef --- /dev/null +++ b/website/src/routes/api/settings/volume/+server.ts @@ -0,0 +1,68 @@ +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'; + +export async function POST({ request }) { + const session = await auth.api.getSession({ + headers: request.headers + }); + + if (!session?.user) { + throw error(401, 'Not authenticated'); + } + + try { + const { master, muted } = await request.json(); + + if (typeof master !== 'number' || master < 0 || master > 1) { + throw error(400, 'Invalid master volume'); + } + if (typeof muted !== 'boolean') { + throw error(400, 'Invalid muted setting'); + } + + await db.update(user) + .set({ + volumeMaster: master.toString(), + volumeMuted: muted, + updatedAt: new Date() + }) + .where(eq(user.id, Number(session.user.id))); + + return json({ success: true }); + } catch (e) { + console.error('Volume settings save failed:', e); + throw error(500, 'Failed to save volume settings'); + } +} + +export async function GET({ request }) { + const session = await auth.api.getSession({ + headers: request.headers + }); + + if (!session?.user) { + throw error(401, 'Not authenticated'); + } + + try { + const userData = await db.select({ + volumeMaster: user.volumeMaster, + volumeMuted: user.volumeMuted + }).from(user).where(eq(user.id, Number(session.user.id))); + + if (!userData[0]) { + throw error(404, 'User not found'); + } + + return json({ + master: parseFloat(userData[0].volumeMaster), + muted: userData[0].volumeMuted + }); + } catch (e) { + console.error('Volume settings load failed:', e); + throw error(500, 'Failed to load volume settings'); + } +} diff --git a/website/src/routes/settings/+page.server.ts b/website/src/routes/settings/+page.server.ts deleted file mode 100644 index ce5ed7f..0000000 --- a/website/src/routes/settings/+page.server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { auth } from '$lib/auth'; -import type { PageServerLoad } from './$types'; -import { error } from '@sveltejs/kit'; - -export const load: PageServerLoad = async (event) => { - const session = await auth.api.getSession({ - headers: event.request.headers - }); - if (!session?.user) throw error(401, 'Not authenticated'); - - return { user: session.user }; -}; diff --git a/website/src/routes/settings/+page.svelte b/website/src/routes/settings/+page.svelte index 064ec09..5eddefb 100644 --- a/website/src/routes/settings/+page.svelte +++ b/website/src/routes/settings/+page.svelte @@ -7,27 +7,28 @@ import { Textarea } from '$lib/components/ui/textarea'; import * as Avatar from '$lib/components/ui/avatar'; import * as Card from '$lib/components/ui/card'; + import { Slider } from '$lib/components/ui/slider'; import { onMount, onDestroy } from 'svelte'; - import { CheckIcon } from 'lucide-svelte'; + import { CheckIcon, Volume2Icon, VolumeXIcon } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import { MAX_FILE_SIZE } from '$lib/data/constants'; + import { volumeSettings } from '$lib/stores/volume-settings'; + import { USER_DATA } from '$lib/stores/user-data'; - let { data } = $props(); + let name = $state($USER_DATA?.name || ''); + let bio = $state($USER_DATA?.bio ?? ''); + let username = $state($USER_DATA?.username || ''); - let name = $state(data.user.name); - let bio = $state(data.user.bio ?? ''); - let username = $state(data.user.username); - - const initialUsername = data.user.username; + const initialUsername = $USER_DATA?.username || ''; let avatarFile: FileList | undefined = $state(undefined); let previewUrl: string | null = $state(null); - let currentAvatarUrl = $derived(previewUrl || getPublicUrl(data.user.image ?? null)); + let currentAvatarUrl = $derived(previewUrl || getPublicUrl($USER_DATA?.image ?? null)); let isDirty = $derived( - name !== data.user.name || - bio !== (data.user.bio ?? '') || - username !== data.user.username || + name !== ($USER_DATA?.name || '') || + bio !== ($USER_DATA?.bio ?? '') || + username !== ($USER_DATA?.username || '') || avatarFile !== undefined ); @@ -37,6 +38,9 @@ let usernameAvailable: boolean | null = $state(null); let checkingUsername = $state(false); + let masterVolume = $state(($USER_DATA?.volumeMaster || 0) * 100); + let isMuted = $state($USER_DATA?.volumeMuted || false); + function beforeUnloadHandler(e: BeforeUnloadEvent) { if (isDirty) { e.preventDefault(); @@ -45,6 +49,8 @@ onMount(() => { window.addEventListener('beforeunload', beforeUnloadHandler); + volumeSettings.setMaster($USER_DATA?.volumeMaster || 0); + volumeSettings.setMuted($USER_DATA?.volumeMuted || false); }); onDestroy(() => { @@ -121,93 +127,167 @@ loading = false; } } + + const debouncedSaveVolume = debounce(async (settings: { master: number; muted: boolean }) => { + try { + const response = await fetch('/api/settings/volume', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(settings) + }); + + if (!response.ok) { + throw new Error('Failed to save volume settings'); + } + } catch (error) { + console.error('Failed to save volume settings:', error); + toast.error('Failed to save volume settings'); + } + }, 500); + + async function saveVolumeToServer(settings: { master: number; muted: boolean }) { + debouncedSaveVolume(settings); + } + + function handleMasterVolumeChange(value: number) { + masterVolume = value; + const normalizedValue = value / 100; + volumeSettings.setMaster(normalizedValue); + saveVolumeToServer({ master: normalizedValue, muted: isMuted }); + } + + function toggleMute() { + isMuted = !isMuted; + volumeSettings.setMuted(isMuted); + saveVolumeToServer({ master: masterVolume / 100, muted: isMuted }); + }

Settings

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

{name}

-

@{username}

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

{name}

+

@{username}

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

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

+
+ +
+ +