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 WireFor(ulong account) => For(account).Select(ToWire).ToList(); public static List For(ulong account) => Col().Find(x => x.AccountId == account).OrderBy(x => x.CreatedAt).ToList(); } }