using LiteDB; namespace Campus_live_rewrite.api.dbs { /// /// Website support tickets. A signed-in player opens a ticket (from the Support page bot's /// "talk to a real person" flow); staff (dev / mod / owner) see EVERY ticket and can reply, /// close and reopen. Stored in its own LiteDB file; checkpointed by DbMaintenance. /// public static class SupportDB { private static readonly LiteDatabase DB = new LiteDatabase(Environment.CurrentDirectory + "/Data/DBs/Support.db"); public static void Checkpoint() => DB.Checkpoint(); private static ILiteCollection Tickets => DB.GetCollection("tickets"); static SupportDB() { Tickets.EnsureIndex(x => x.AccountId); Tickets.EnsureIndex(x => x.Status); } public sealed class TicketMessage { public ulong FromAccountId { get; set; } public string FromName { get; set; } = ""; public bool Staff { get; set; } public string Body { get; set; } = ""; public DateTime At { get; set; } = DateTime.UtcNow; } public sealed class Ticket { public int Id { get; set; } // LiteDB auto-increment PK public ulong AccountId { get; set; } public string Username { get; set; } = ""; public string Subject { get; set; } = ""; public string Status { get; set; } = "open"; // "open" | "closed" public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public List Messages { get; set; } = new(); } public static Ticket Create(ulong accountId, string username, string subject, string body) { var t = new Ticket { AccountId = accountId, Username = username, Subject = string.IsNullOrWhiteSpace(subject) ? "Support request" : subject, Status = "open", }; t.Messages.Add(new TicketMessage { FromAccountId = accountId, FromName = username, Staff = false, Body = body }); Tickets.Insert(t); // Id auto-assigned return t; } public static Ticket? Get(int id) => Tickets.FindById(id); public static List ForAccount(ulong accountId) => Tickets.Find(x => x.AccountId == accountId).OrderByDescending(x => x.UpdatedAt).ToList(); public static List All(string? status) { var rows = status == "open" ? Tickets.Find(x => x.Status == "open") : status == "closed" ? Tickets.Find(x => x.Status == "closed") : Tickets.FindAll(); return rows.OrderByDescending(x => x.UpdatedAt).ToList(); } public static bool AddMessage(int id, ulong fromId, string fromName, bool staff, string body) { var t = Tickets.FindById(id); if (t == null) return false; t.Messages.Add(new TicketMessage { FromAccountId = fromId, FromName = fromName, Staff = staff, Body = body }); t.UpdatedAt = DateTime.UtcNow; // A reply on a closed ticket reopens it, so nobody's follow-up is lost in a closed thread. if (t.Status == "closed") t.Status = "open"; Tickets.Update(t); return true; } public static bool SetStatus(int id, string status) { var t = Tickets.FindById(id); if (t == null) return false; t.Status = status; t.UpdatedAt = DateTime.UtcNow; Tickets.Update(t); return true; } public static int OpenCount() => Tickets.Count(x => x.Status == "open"); } }