206 lines
6.9 KiB
C#
206 lines
6.9 KiB
C#
#if UNITY_EDITOR
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using System.Text.Json;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using Supabase;
|
||
using Postgrest;
|
||
using Postgrest.Attributes;
|
||
using Postgrest.Models;
|
||
|
||
#region DB Model
|
||
|
||
[Table("players")]
|
||
public class PlayerRow : BaseModel
|
||
{
|
||
[PrimaryKey("wallet_address", false)]
|
||
[Column("wallet_address")] public string WalletAddress { get; set; }
|
||
|
||
[Column("total_kills")] public int TotalKills { get; set; }
|
||
[Column("average_placement")] public decimal? AveragePlacement { get; set; }
|
||
[Column("win_percentage")] public decimal? WinPercentage { get; set; }
|
||
[Column("in_game_currency")] public long InGameCurrency { get; set; }
|
||
|
||
// jsonb – we'll send/receive as a plain object map
|
||
[Column("purchased_items")] public Dictionary<string, object> PurchasedItems { get; set; } = new();
|
||
|
||
[Column("updated_at")] public DateTimeOffset? UpdatedAt { get; set; }
|
||
}
|
||
|
||
#endregion
|
||
|
||
public class PlayersDevTools : EditorWindow
|
||
{
|
||
// Inputs
|
||
string wallet = "dev_wallet_test_1";
|
||
int totalKills = 3;
|
||
double avgPlacement = 2.5;
|
||
double winPct = 55.25;
|
||
long currency = 1000;
|
||
string purchasedJson = "{\"starter_pack\":true,\"sword\":\"iron\"}";
|
||
|
||
Supabase.Client client;
|
||
|
||
[MenuItem("Tools/Supabase/Players Tester")]
|
||
public static void Open() => GetWindow<PlayersDevTools>("Players Tester");
|
||
|
||
async void OnEnable() => await EnsureClient();
|
||
|
||
async Task EnsureClient()
|
||
{
|
||
if (client != null) return;
|
||
var s = await SupabaseSecretsLoader.LoadAsync(); // editor dev json or Resources
|
||
var opts = new SupabaseOptions { AutoRefreshToken = true, AutoConnectRealtime = false, Schema = "public" };
|
||
client = new Supabase.Client(s.url, s.anonKey, opts);
|
||
await client.InitializeAsync();
|
||
}
|
||
|
||
void OnGUI()
|
||
{
|
||
EditorGUILayout.LabelField("Upsert / Read / Delete: public.players", EditorStyles.boldLabel);
|
||
EditorGUILayout.Space();
|
||
|
||
wallet = EditorGUILayout.TextField("wallet_address", wallet);
|
||
totalKills = EditorGUILayout.IntField("total_kills", totalKills);
|
||
avgPlacement = EditorGUILayout.DoubleField("average_placement", avgPlacement);
|
||
winPct = EditorGUILayout.DoubleField("win_percentage", winPct);
|
||
currency = EditorGUILayout.LongField("in_game_currency", currency);
|
||
purchasedJson = EditorGUILayout.TextField("purchased_items (json)", purchasedJson);
|
||
|
||
EditorGUILayout.Space();
|
||
using (new EditorGUILayout.HorizontalScope())
|
||
{
|
||
if (GUILayout.Button("UPSERT")) _ = UpsertAsync();
|
||
if (GUILayout.Button("READ")) _ = ReadAsync();
|
||
if (GUILayout.Button("DELETE")) _ = DeleteAsync();
|
||
}
|
||
}
|
||
|
||
async Task UpsertAsync()
|
||
{
|
||
try
|
||
{
|
||
await EnsureClient();
|
||
|
||
var row = new PlayerRow
|
||
{
|
||
WalletAddress = wallet,
|
||
TotalKills = totalKills,
|
||
AveragePlacement = (decimal)avgPlacement,
|
||
WinPercentage = (decimal)winPct,
|
||
InGameCurrency = currency,
|
||
PurchasedItems = ParseDict(purchasedJson),
|
||
UpdatedAt = DateTimeOffset.UtcNow
|
||
};
|
||
|
||
// postgrest-csharp 3.5.x – Upsert by PK, no onConflict param
|
||
var resp = await client.From<PlayerRow>().Upsert(row);
|
||
var saved = resp.Models.Count > 0 ? resp.Models[0] : row;
|
||
|
||
Debug.Log($"✅ UPSERT OK -> wallet={saved.WalletAddress} kills={saved.TotalKills} currency={saved.InGameCurrency}");
|
||
ShowNotification(new GUIContent("UPSERT OK"));
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"UPSERT FAILED: {e.Message}");
|
||
ShowNotification(new GUIContent("UPSERT FAILED"));
|
||
}
|
||
}
|
||
|
||
async Task ReadAsync()
|
||
{
|
||
try
|
||
{
|
||
await EnsureClient();
|
||
|
||
var resp = await client
|
||
.From<PlayerRow>()
|
||
.Select("*") // 3.5.x needs a string
|
||
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, wallet)
|
||
.Get();
|
||
|
||
if (resp.Models.Count == 0)
|
||
{
|
||
Debug.LogWarning("No row found.");
|
||
ShowNotification(new GUIContent("NOT FOUND"));
|
||
return;
|
||
}
|
||
|
||
var p = resp.Models[0];
|
||
Debug.Log($"✅ READ -> wallet={p.WalletAddress}, kills={p.TotalKills}, avg={p.AveragePlacement}, win%={p.WinPercentage}, cur={p.InGameCurrency}, items={JsonSerializer.Serialize(p.PurchasedItems)}");
|
||
ShowNotification(new GUIContent("READ OK"));
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"READ FAILED: {e.Message}");
|
||
ShowNotification(new GUIContent("READ FAILED"));
|
||
}
|
||
}
|
||
|
||
async Task DeleteAsync()
|
||
{
|
||
try
|
||
{
|
||
await EnsureClient();
|
||
|
||
await client
|
||
.From<PlayerRow>()
|
||
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, wallet)
|
||
.Delete();
|
||
|
||
Debug.Log("✅ DELETE OK");
|
||
ShowNotification(new GUIContent("DELETE OK"));
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"DELETE FAILED: {e.Message}");
|
||
ShowNotification(new GUIContent("DELETE FAILED"));
|
||
}
|
||
}
|
||
|
||
// -------- JSON helpers (System.Text.Json) --------
|
||
static Dictionary<string, object> ParseDict(string json)
|
||
{
|
||
var result = new Dictionary<string, object>();
|
||
if (string.IsNullOrWhiteSpace(json)) return result;
|
||
|
||
try
|
||
{
|
||
using var doc = JsonDocument.Parse(json);
|
||
if (doc.RootElement.ValueKind != JsonValueKind.Object) return result;
|
||
|
||
foreach (var p in doc.RootElement.EnumerateObject())
|
||
result[p.Name] = ToPlain(p.Value);
|
||
}
|
||
catch { /* ignore -> empty dict */ }
|
||
return result;
|
||
}
|
||
|
||
static object ToPlain(JsonElement e)
|
||
{
|
||
switch (e.ValueKind)
|
||
{
|
||
case JsonValueKind.String: return e.GetString();
|
||
case JsonValueKind.Number:
|
||
if (e.TryGetInt64(out var l)) return l;
|
||
if (e.TryGetDouble(out var d)) return d;
|
||
return e.GetRawText();
|
||
case JsonValueKind.True: return true;
|
||
case JsonValueKind.False: return false;
|
||
case JsonValueKind.Null: return null;
|
||
case JsonValueKind.Array:
|
||
var list = new List<object>();
|
||
foreach (var it in e.EnumerateArray()) list.Add(ToPlain(it));
|
||
return list;
|
||
case JsonValueKind.Object:
|
||
var dict = new Dictionary<string, object>();
|
||
foreach (var p in e.EnumerateObject()) dict[p.Name] = ToPlain(p.Value);
|
||
return dict;
|
||
default: return e.GetRawText();
|
||
}
|
||
}
|
||
}
|
||
#endif
|