using LiteDB;
namespace Campus_live_rewrite.api.dbs
{
///
/// Item wishlists (POST /api/itemWishlists/v1/wishlist/add|remove). The client's add 405'd
/// (only the reads existed); the row is recorded here so the add answers and the panel can
/// read it back. The wishlist DTO the client renders is not pinned yet, so the reads stay
/// empty until it is (see the wishlist routes) - nothing is lost, the rows are here.
///
public static class WishlistDB
{
private static readonly Lazy _db = new(() => new LiteDatabase(Path.Combine("Data", "DBs", "Wishlists.db")));
public class Row
{
[BsonId] public string Key { get; set; } = ""; // "{account}|{storefront}|{item}"
public ulong AccountId { get; set; }
public int StorefrontType { get; set; }
public int PurchasableItemId { get; set; }
// A UGC (custom avatar item) wish: PurchasableItemId is -1 and this is the item's GUID.
public string? CustomAvatarItemId { get; set; }
public string RawBody { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
private static ILiteCollection Col()
{
var c = _db.Value.GetCollection("items");
c.EnsureIndex(x => x.AccountId);
return c;
}
private static string KeyFor(ulong account, int item, string? custom) =>
string.IsNullOrWhiteSpace(custom) ? $"{account}|{item}" : $"{account}|ugc|{custom.ToLowerInvariant()}";
/// Record a wish; returns the row (existing or new) so the add can answer it.
public static Row Add(ulong account, int storefront, int item, string? custom, string raw)
{
var key = KeyFor(account, item, custom);
try
{
var existing = Col().FindById(key);
if (existing != null) return existing;
var row = new Row { Key = key, AccountId = account, StorefrontType = storefront, PurchasableItemId = item, CustomAvatarItemId = string.IsNullOrWhiteSpace(custom) ? null : custom, RawBody = raw };
Col().Upsert(row);
return row;
}
catch (Exception ex)
{
Console.WriteLine($"[wishlist] add failed: {ex.Message}");
return new Row { Key = key, AccountId = account, StorefrontType = storefront, PurchasableItemId = item, CustomAvatarItemId = custom };
}
}
public static void Remove(ulong account, int item, string? custom = null)
{
try
{
if (!string.IsNullOrWhiteSpace(custom))
{
string c = custom.ToLowerInvariant();
Col().DeleteMany(x => x.AccountId == account && x.CustomAvatarItemId != null && x.CustomAvatarItemId.ToLower() == c);
}
else Col().DeleteMany(x => x.AccountId == account && x.PurchasableItemId == item && x.CustomAvatarItemId == null);
}
catch (Exception ex) { Console.WriteLine($"[wishlist] remove failed: {ex.Message}"); }
}
/// The wire entry (real RecNet: WishlistItemId guid, AccountId, PurchasableItemId, CreatedAt;
/// the 2025 client adds CustomAvatarItemId for UGC wishes). The guid is derived from the key so it is stable.
public static Guid WireId(Row r)
{
using var md5 = System.Security.Cryptography.MD5.Create();
return new Guid(md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes("wish|" + r.Key)));
}
public static object ToWire(Row r)
{
Guid id = WireId(r);
return new
{
WishlistItemId = id,
AccountId = r.AccountId,
PurchasableItemId = r.PurchasableItemId,
CustomAvatarItemId = r.CustomAvatarItemId,
CreatedAt = r.CreatedAt
};
}
public static List