288 lines
11 KiB
C#
288 lines
11 KiB
C#
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 won’t 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 3–24 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 3–24 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;
|
||
}
|
||
|
||
}
|