2025-10-15 20:28:33 +04:00

288 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Supabase;
using Postgrest;
using Game.Data;
using UnityEngine; // for PlayerPrefs
using System.Text.RegularExpressions;
public static class GameDb
{
private static Supabase.Client _client;
private const string PrefsWalletKey = "WALLET_ADDRESS";
private const string PrefsName = "DISPLAY_NAME";
public static async Task InitAsync()
{
if (_client != null) return;
var s = await SupabaseSecretsLoader.LoadAsync();
var opts = new SupabaseOptions { AutoConnectRealtime = false, AutoRefreshToken = true, Schema = "public" };
_client = new Supabase.Client(s.url, s.anonKey, opts);
await _client.InitializeAsync();
}
// ------------------------------------------------------------
// WalletConnected: check if exists, create if missing, store in PlayerPrefs
// ------------------------------------------------------------
//public static async Task<PlayerRecord> WalletConnectedAsync(string walletAddress)
//{
// if (string.IsNullOrWhiteSpace(walletAddress))
// throw new ArgumentException("walletAddress cannot be empty.");
// await InitAsync();
// // exists?
// var existing = await _client.From<PlayerRecord>()
// .Select("*")
// .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
// .Get();
// PlayerRecord result;
// if (existing.Models.Count > 0)
// {
// result = existing.Models[0];
// }
// else
// {
// // insert minimal row (DB supplies defaults & computed columns)
// var insert = await _client.From<PlayerRecord>().Insert(new PlayerRecord { WalletAddress = walletAddress });
// result = insert.Models.Count > 0 ? insert.Models[0] : new PlayerRecord { WalletAddress = walletAddress };
// }
// // "login success": save wallet locally
// PlayerPrefs.SetString(PrefsWalletKey, walletAddress);
// PlayerPrefs.Save();
// return result;
//}
public static async Task<PlayerRecord> WalletConnectedAsync(string walletAddress)
{
if (string.IsNullOrWhiteSpace(walletAddress))
throw new ArgumentException("walletAddress cannot be empty.");
await InitAsync();
// Atomic, safe, and wont try to write generated columns.
var rows = await _client.Rpc<List<PlayerRecord>>("ensure_player", new { p_wallet = walletAddress });
var player = rows != null && rows.Count > 0 ? rows[0] : null;
if (player == null)
throw new Exception("ensure_player RPC returned no row.");
PlayerPrefs.SetString("WALLET_ADDRESS", walletAddress);
PlayerPrefs.Save();
return player;
}
// ------------------------------------------------------------
// AddKill (+1 by default) -- uses RPC for atomic increment
// ------------------------------------------------------------
public static async Task<PlayerRecord> AddKillAsync(string walletAddress, int delta = 1)
{
await InitAsync();
var payload = new { p_wallet = walletAddress, p_delta = delta };
// SQL returns SETOF players → deserialize to a list/array and take first
var rows = await _client.Rpc<List<PlayerRecord>>("add_kills", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// Call Supabase-side method to add currency (RPC)
// ------------------------------------------------------------
public static async Task<PlayerRecord> AddCurrencyAsync(string walletAddress, long delta)
{
await InitAsync();
var payload = new { p_wallet = walletAddress, p_delta = delta };
var rows = await _client.Rpc<List<PlayerRecord>>("add_currency", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// Get currency for player
// ------------------------------------------------------------
public static async Task<long> GetCurrencyAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("in_game_currency")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
return resp.Models.Count > 0 ? resp.Models[0].InGameCurrency : 0L;
}
// ------------------------------------------------------------
// Get JSON for bought items
// ------------------------------------------------------------
public static async Task<string> GetPurchasedItemsJsonAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("purchased_items")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
if (resp.Models.Count == 0) return "{}";
var dict = resp.Models[0].PurchasedItems ?? new Dictionary<string, object>();
return JsonSerializer.Serialize(dict);
}
// ------------------------------------------------------------
// Edit JSON for bought items (replace with provided JSON)
// ------------------------------------------------------------
public static async Task<PlayerRecord> SetPurchasedItemsJsonAsync(string walletAddress, string json)
{
await InitAsync();
var rows = await _client.Rpc<List<PlayerRecord>>(
"set_purchased_items",
new { p_wallet = walletAddress, p_items = System.Text.Json.JsonDocument.Parse(json).RootElement }
);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// +1 games played / +1 games won (RPCs)
// ------------------------------------------------------------
public static async Task<PlayerRecord> IncGamesPlayedAsync(string walletAddress)
{
await InitAsync();
var rows = await _client.Rpc<List<PlayerRecord>>("inc_games_played", new { p_wallet = walletAddress });
return rows != null && rows.Count > 0 ? rows[0] : null;
}
public static async Task<PlayerRecord> IncGamesWonAsync(string walletAddress)
{
await InitAsync();
var rows = await _client.Rpc<List<PlayerRecord>>("inc_games_won", new { p_wallet = walletAddress });
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// Get Player data (full record)
// ------------------------------------------------------------
public static async Task<PlayerRecord> GetPlayerAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("*")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
return resp.Models.Count > 0 ? resp.Models[0] : null;
}
public static async Task<PlayerRecord> SendGameWinAsync(
string walletAddress, int placement)
{
await InitAsync();
var payload = new
{
p_wallet = walletAddress,
p_placement = placement
};
var rows = await _client.Rpc<List<PlayerRecord>>("record_game_win", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
/// <summary>
/// Add/overwrite one entry in purchased_items: key -> value (value default true).
/// </summary>
public static async Task<PlayerRecord> AddPurchasedItemAsync(string walletAddress, string key, object value = null)
{
await InitAsync();
// Fallback: serialize -> parse -> take RootElement
JsonElement jsonb = value == null
? JsonDocument.Parse("true").RootElement
: JsonDocument.Parse(JsonSerializer.Serialize(value)).RootElement;
var payload = new { p_wallet = walletAddress, p_key = key, p_value = jsonb };
var rows = await _client.Rpc<List<PlayerRecord>>("add_purchased_item", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
static string SanitizeName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return null;
name = name.Trim();
// allow letters, numbers, space, underscore, dash; clamp length 3..24
name = Regex.Replace(name, @"[^A-Za-z0-9 _\-]", "");
if (name.Length < 3) return null;
if (name.Length > 24) name = name.Substring(0, 24);
return name;
}
// -------- GETTER --------
public static async Task<string> GetPlayerNameAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("display_name")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
var name = resp.Models.Count > 0 ? resp.Models[0].DisplayName ?? "" : "";
// cache (optional)
PlayerPrefs.SetString(PrefsName, name);
PlayerPrefs.Save();
return name;
}
// -------- CHECK EMPTY (DB) --------
public static async Task<bool> IsNameEmptyAsync(string walletAddress)
{
var name = await GetPlayerNameAsync(walletAddress);
return string.IsNullOrEmpty(name);
}
// -------- SETTER: only if empty (safe path) --------
// Calls your SQL function: set_name_if_empty(p_wallet, p_name)
public static async Task<PlayerRecord> SetNameIfEmptyAsync(string walletAddress, string proposedName)
{
await InitAsync();
var clean = SanitizeName(proposedName);
if (string.IsNullOrEmpty(clean))
throw new ArgumentException("Invalid name. Use 324 chars: letters, numbers, space, _ or -.");
// RPC returns the player row (whether it updated or not)
var rows = await _client.Rpc<List<PlayerRecord>>(
"set_name_if_empty",
new { p_wallet = walletAddress, p_name = clean }
);
var player = rows != null && rows.Count > 0 ? rows[0] : null;
if (player == null) throw new Exception("set_name_if_empty returned no row.");
PlayerPrefs.SetString(PrefsName, player.DisplayName ?? "");
PlayerPrefs.Save();
return player;
}
// -------- SETTER: force update (optional, for rename UI) --------
public static async Task<PlayerRecord> SetNameAsync(string walletAddress, string newName)
{
await InitAsync();
var clean = SanitizeName(newName);
if (string.IsNullOrEmpty(clean))
throw new ArgumentException("Invalid name. Use 324 chars: letters, numbers, space, _ or -.");
// RPC returns the row; no SDK .Set() nonsense, no risk of overwriting other fields
var rows = await _client.Rpc<List<PlayerRecord>>(
"set_name",
new { p_wallet = walletAddress, p_name = clean }
);
var rec = rows != null && rows.Count > 0 ? rows[0] : null;
if (rec == null) throw new Exception("Name update failed.");
PlayerPrefs.SetString("DISPLAY_NAME", rec.DisplayName ?? "");
PlayerPrefs.Save();
return rec;
}
}