using Microsoft.EntityFrameworkCore; using Npgsql; namespace WorldTime.Data; public class BotDatabaseContext : DbContext { private static readonly string _connectionString; static BotDatabaseContext() { // Get our own config loaded just for the SQL stuff var conf = new Configuration(); _connectionString = new NpgsqlConnectionStringBuilder() { Host = conf.SqlHost ?? "localhost", // default to localhost Database = conf.SqlDatabase, Username = conf.SqlUsername, Password = conf.SqlPassword }.ToString(); } public DbSet UserEntries { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .UseNpgsql(_connectionString) .UseSnakeCaseNamingConvention(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { entity.HasKey(e => new { e.GuildId, e.UserId }).HasName("userdata_pkey"); }); } #region Helper methods / abstractions /// /// Checks if a given guild contains at least one user data entry with recent enough activity. ///
To be used within a context. ///
internal bool HasAnyUsers(SocketGuild guild) => UserEntries.Where(u => u.GuildId == (long)guild.Id).Any(); /// /// Gets the number of unique time zones in the database. ///
To be used within a context. ///
internal int GetDistinctZoneCount() => UserEntries.Select(u => u.TimeZone).Distinct().Count(); /// /// Removes the specified user from the database. ///
To be used within a context. ///
/// /// if the removal was successful. /// if the user did not exist. /// internal bool DeleteUser(SocketGuildUser user) { var tuser = UserEntries.Where(u => u.UserId == (long)user.Id && u.GuildId == (long)user.Guild.Id).SingleOrDefault(); if (tuser != null) { Remove(tuser); SaveChanges(); return true; } else { return false; } } /// /// Inserts/updates the specified user in the database. ///
To be used within a context. ///
internal void UpdateUser(SocketGuildUser user, string timezone) { var tuser = UserEntries.Where(u => u.UserId == (long)user.Id && u.GuildId == (long)user.Guild.Id).SingleOrDefault(); if (tuser != null) { Update(tuser); } else { tuser = new UserEntry() { UserId = (long)user.Id, GuildId = (long)user.Guild.Id }; Add(tuser); } tuser.TimeZone = timezone; SaveChanges(); } /// /// Retrieves the time zone name of a single user. ///
To be used within a context. ///
internal string? GetUserZone(SocketGuildUser user) { var tuser = UserEntries.Where(u => u.UserId == (long)user.Id && u.GuildId == (long)user.Guild.Id).SingleOrDefault(); return tuser?.TimeZone; } /// /// Retrieves all known user time zones for the given guild. ///
To be used within a context. ///
/// /// An unsorted dictionary. Keys are time zones, values are user IDs representative of those zones. /// internal Dictionary> GetGuildZones(ulong guildId) { // Implementing the query from the previous iteration, in which further filtering is done by the caller. // TODO consider bringing filtering back to this step, if there may be any advantage var query = from entry in UserEntries where entry.GuildId == (long)guildId orderby entry.UserId select Tuple.Create(entry.TimeZone, (ulong)entry.UserId); var resultSet = new Dictionary>(); foreach (var (tz, user) in query) { if (!resultSet.ContainsKey(tz)) resultSet.Add(tz, new List()); resultSet[tz].Add(user); } return resultSet; } #endregion }