mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-24 17:34:13 +00:00
Update style for data classes
This commit is contained in:
parent
fdffa5425c
commit
160152a0b4
3 changed files with 359 additions and 389 deletions
|
@ -1,32 +1,26 @@
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BirthdayBot.Data
|
namespace BirthdayBot.Data;
|
||||||
{
|
|
||||||
|
internal static class Database {
|
||||||
|
public static string DBConnectionString { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Database access and some abstractions.
|
/// Sets up and opens a database connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class Database
|
public static async Task<NpgsqlConnection> OpenConnectionAsync() {
|
||||||
{
|
var db = new NpgsqlConnection(DBConnectionString);
|
||||||
public static string DBConnectionString { get; set; }
|
await db.OpenAsync().ConfigureAwait(false);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<NpgsqlConnection> OpenConnectionAsync()
|
public static async Task DoInitialDatabaseSetupAsync() {
|
||||||
{
|
using var db = await OpenConnectionAsync().ConfigureAwait(false);
|
||||||
if (DBConnectionString == null) throw new Exception("Database connection string not set");
|
|
||||||
var db = new NpgsqlConnection(DBConnectionString);
|
|
||||||
await db.OpenAsync().ConfigureAwait(false);
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task DoInitialDatabaseSetupAsync()
|
// Refer to the methods being called for information on how the database is set up.
|
||||||
{
|
// Note: The order these are called is important. (Foreign reference constraints.)
|
||||||
using var db = await OpenConnectionAsync().ConfigureAwait(false);
|
await GuildConfiguration.DatabaseSetupAsync(db).ConfigureAwait(false);
|
||||||
|
await GuildUserConfiguration.DatabaseSetupAsync(db).ConfigureAwait(false);
|
||||||
// Refer to the methods being called for information on how the database is set up.
|
|
||||||
// Note: The order these are called is important. (Foreign reference constraints.)
|
|
||||||
await GuildConfiguration.DatabaseSetupAsync(db).ConfigureAwait(false);
|
|
||||||
await GuildUserConfiguration.DatabaseSetupAsync(db).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,258 +6,244 @@ using System.Data.Common;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BirthdayBot.Data
|
namespace BirthdayBot.Data;
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents guild-specific configuration as exists in the database.
|
||||||
|
/// Updating any property requires a call to <see cref="UpdateAsync"/> for changes to take effect.
|
||||||
|
/// </summary>
|
||||||
|
class GuildConfiguration {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents guild-specific configuration as exists in the database.
|
/// Gets this configuration's corresponding guild ID.
|
||||||
/// Updating any property requires a call to <see cref="UpdateAsync"/> for changes to take effect.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class GuildConfiguration
|
public ulong GuildId { get; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets this configuration's corresponding guild ID.
|
|
||||||
/// </summary>
|
|
||||||
public ulong GuildId { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the guild's designated usable role ID.
|
/// Gets or sets the guild's designated usable role ID.
|
||||||
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ulong? RoleId { get; set; }
|
public ulong? RoleId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the announcement channel ID.
|
/// Gets or sets the announcement channel ID.
|
||||||
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ulong? AnnounceChannelId { get; set; }
|
public ulong? AnnounceChannelId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the guild's default time zone ztring.
|
/// Gets or sets the guild's default time zone ztring.
|
||||||
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TimeZone { get; set; }
|
public string TimeZone { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the guild's moderated mode setting.
|
/// Gets or sets the guild's moderated mode setting.
|
||||||
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsModerated { get; set; }
|
public bool IsModerated { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the guild's corresponding bot moderator role ID.
|
/// Gets or sets the guild's corresponding bot moderator role ID.
|
||||||
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ulong? ModeratorRole { get; set; }
|
public ulong? ModeratorRole { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the guild-specific birthday announcement message.
|
/// Gets or sets the guild-specific birthday announcement message.
|
||||||
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public (string, string) AnnounceMessages { get; set; }
|
public (string, string) AnnounceMessages { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the announcement ping setting.
|
/// Gets or sets the announcement ping setting.
|
||||||
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
/// Updating this value requires a call to <see cref="UpdateAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AnnouncePing { get; set; }
|
public bool AnnouncePing { get; set; }
|
||||||
|
|
||||||
// Called by Load. Double-check ordinals when changes are made.
|
// Called by Load. Double-check ordinals when changes are made.
|
||||||
private GuildConfiguration(DbDataReader reader)
|
private GuildConfiguration(DbDataReader reader) {
|
||||||
{
|
GuildId = (ulong)reader.GetInt64(0);
|
||||||
GuildId = (ulong)reader.GetInt64(0);
|
if (!reader.IsDBNull(1)) RoleId = (ulong)reader.GetInt64(1);
|
||||||
if (!reader.IsDBNull(1)) RoleId = (ulong)reader.GetInt64(1);
|
if (!reader.IsDBNull(2)) AnnounceChannelId = (ulong)reader.GetInt64(2);
|
||||||
if (!reader.IsDBNull(2)) AnnounceChannelId = (ulong)reader.GetInt64(2);
|
TimeZone = reader.IsDBNull(3) ? null : reader.GetString(3);
|
||||||
TimeZone = reader.IsDBNull(3) ? null : reader.GetString(3);
|
IsModerated = reader.GetBoolean(4);
|
||||||
IsModerated = reader.GetBoolean(4);
|
if (!reader.IsDBNull(5)) ModeratorRole = (ulong)reader.GetInt64(5);
|
||||||
if (!reader.IsDBNull(5)) ModeratorRole = (ulong)reader.GetInt64(5);
|
string announceMsg = reader.IsDBNull(6) ? null : reader.GetString(6);
|
||||||
string announceMsg = reader.IsDBNull(6) ? null : reader.GetString(6);
|
string announceMsgPl = reader.IsDBNull(7) ? null : reader.GetString(7);
|
||||||
string announceMsgPl = reader.IsDBNull(7) ? null : reader.GetString(7);
|
AnnounceMessages = (announceMsg, announceMsgPl);
|
||||||
AnnounceMessages = (announceMsg, announceMsgPl);
|
AnnouncePing = reader.GetBoolean(8);
|
||||||
AnnouncePing = reader.GetBoolean(8);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the given user exists in the block list.
|
/// Checks if the given user exists in the block list.
|
||||||
/// If the server is in moderated mode, this always returns true.
|
/// If the server is in moderated mode, this always returns true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<bool> IsUserBlockedAsync(ulong userId)
|
public async Task<bool> IsUserBlockedAsync(ulong userId) {
|
||||||
{
|
if (IsModerated) return true;
|
||||||
if (IsModerated) return true;
|
|
||||||
|
|
||||||
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
||||||
using var c = db.CreateCommand();
|
using var c = db.CreateCommand();
|
||||||
c.CommandText = $"select * from {BackingTableBans} "
|
c.CommandText = $"select * from {BackingTableBans} "
|
||||||
+ "where guild_id = @Gid and user_id = @Uid";
|
+ "where guild_id = @Gid and user_id = @Uid";
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
||||||
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
||||||
c.Prepare();
|
c.Prepare();
|
||||||
using var r = await c.ExecuteReaderAsync().ConfigureAwait(false);
|
using var r = await c.ExecuteReaderAsync().ConfigureAwait(false);
|
||||||
if (!await r.ReadAsync().ConfigureAwait(false)) return false;
|
if (!await r.ReadAsync().ConfigureAwait(false)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the specified user to the block list corresponding to this guild.
|
/// Adds the specified user to the block list corresponding to this guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task BlockUserAsync(ulong userId)
|
public async Task BlockUserAsync(ulong userId) {
|
||||||
{
|
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
||||||
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
using var c = db.CreateCommand();
|
||||||
using var c = db.CreateCommand();
|
c.CommandText = $"insert into {BackingTableBans} (guild_id, user_id) "
|
||||||
c.CommandText = $"insert into {BackingTableBans} (guild_id, user_id) "
|
+ "values (@Gid, @Uid) "
|
||||||
+ "values (@Gid, @Uid) "
|
+ "on conflict (guild_id, user_id) do nothing";
|
||||||
+ "on conflict (guild_id, user_id) do nothing";
|
// There is no validation on whether the requested user is even in the guild. will this be a problem?
|
||||||
// There is no validation on whether the requested user is even in the guild. will this be a problem?
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
||||||
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
c.Prepare();
|
||||||
c.Prepare();
|
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the specified user from the block list corresponding to this guild.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if a user has been removed, false if the requested user was not in this list.</returns>
|
||||||
|
public async Task<bool> UnblockUserAsync(ulong userId) {
|
||||||
|
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
||||||
|
using var c = db.CreateCommand();
|
||||||
|
c.CommandText = $"delete from {BackingTableBans} where "
|
||||||
|
+ "guild_id = @Gid and user_id = @Uid";
|
||||||
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
||||||
|
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
||||||
|
c.Prepare();
|
||||||
|
var result = await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
|
return result != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given user can be considered a bot moderator.
|
||||||
|
/// Checks for either the Manage Guild permission or if the user is within a predetermined role.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBotModerator(SocketGuildUser user)
|
||||||
|
=> user.GuildPermissions.ManageGuild || (ModeratorRole.HasValue && user.Roles.Any(r => r.Id == ModeratorRole.Value));
|
||||||
|
|
||||||
|
#region Database
|
||||||
|
public const string BackingTable = "settings";
|
||||||
|
public const string BackingTableBans = "banned_users";
|
||||||
|
|
||||||
|
internal static async Task DatabaseSetupAsync(NpgsqlConnection db) {
|
||||||
|
using (var c = db.CreateCommand()) {
|
||||||
|
c.CommandText = $"create table if not exists {BackingTable} ("
|
||||||
|
+ "guild_id bigint primary key, "
|
||||||
|
+ "role_id bigint null, "
|
||||||
|
+ "channel_announce_id bigint null, "
|
||||||
|
+ "time_zone text null, "
|
||||||
|
+ "moderated boolean not null default FALSE, "
|
||||||
|
+ "moderator_role bigint null, "
|
||||||
|
+ "announce_message text null, "
|
||||||
|
+ "announce_message_pl text null, "
|
||||||
|
+ "announce_ping boolean not null default FALSE, "
|
||||||
|
+ "last_seen timestamptz not null default NOW()"
|
||||||
|
+ ")";
|
||||||
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
using (var c = db.CreateCommand()) {
|
||||||
/// <summary>
|
c.CommandText = $"create table if not exists {BackingTableBans} ("
|
||||||
/// Removes the specified user from the block list corresponding to this guild.
|
+ $"guild_id bigint not null references {BackingTable} ON DELETE CASCADE, "
|
||||||
/// </summary>
|
+ "user_id bigint not null, "
|
||||||
/// <returns>True if a user has been removed, false if the requested user was not in this list.</returns>
|
+ "PRIMARY KEY (guild_id, user_id)"
|
||||||
public async Task<bool> UnblockUserAsync(ulong userId)
|
+ ")";
|
||||||
{
|
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
|
||||||
using var c = db.CreateCommand();
|
|
||||||
c.CommandText = $"delete from {BackingTableBans} where "
|
|
||||||
+ "guild_id = @Gid and user_id = @Uid";
|
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
|
||||||
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
|
||||||
c.Prepare();
|
|
||||||
var result = await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
|
||||||
return result != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given user can be considered a bot moderator.
|
|
||||||
/// Checks for either the Manage Guild permission or if the user is within a predetermined role.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsBotModerator(SocketGuildUser user)
|
|
||||||
=> user.GuildPermissions.ManageGuild || (ModeratorRole.HasValue && user.Roles.Any(r => r.Id == ModeratorRole.Value));
|
|
||||||
|
|
||||||
#region Database
|
|
||||||
public const string BackingTable = "settings";
|
|
||||||
public const string BackingTableBans = "banned_users";
|
|
||||||
|
|
||||||
internal static async Task DatabaseSetupAsync(NpgsqlConnection db)
|
|
||||||
{
|
|
||||||
using (var c = db.CreateCommand())
|
|
||||||
{
|
|
||||||
c.CommandText = $"create table if not exists {BackingTable} ("
|
|
||||||
+ "guild_id bigint primary key, "
|
|
||||||
+ "role_id bigint null, "
|
|
||||||
+ "channel_announce_id bigint null, "
|
|
||||||
+ "time_zone text null, "
|
|
||||||
+ "moderated boolean not null default FALSE, "
|
|
||||||
+ "moderator_role bigint null, "
|
|
||||||
+ "announce_message text null, "
|
|
||||||
+ "announce_message_pl text null, "
|
|
||||||
+ "announce_ping boolean not null default FALSE, "
|
|
||||||
+ "last_seen timestamptz not null default NOW()"
|
|
||||||
+ ")";
|
|
||||||
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
using (var c = db.CreateCommand())
|
|
||||||
{
|
|
||||||
c.CommandText = $"create table if not exists {BackingTableBans} ("
|
|
||||||
+ $"guild_id bigint not null references {BackingTable} ON DELETE CASCADE, "
|
|
||||||
+ "user_id bigint not null, "
|
|
||||||
+ "PRIMARY KEY (guild_id, user_id)"
|
|
||||||
+ ")";
|
|
||||||
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fetches guild settings from the database. If no corresponding entry exists, it will be created.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="nullIfUnknown">
|
|
||||||
/// If true, this method shall not create a new entry and will return null if the guild does
|
|
||||||
/// not exist in the database.
|
|
||||||
/// </param>
|
|
||||||
public static async Task<GuildConfiguration> LoadAsync(ulong guildId, bool nullIfUnknown)
|
|
||||||
{
|
|
||||||
using (var db = await Database.OpenConnectionAsync().ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
using (var c = db.CreateCommand())
|
|
||||||
{
|
|
||||||
// Take note of ordinals for the constructor
|
|
||||||
c.CommandText = "select guild_id, role_id, channel_announce_id, time_zone, "
|
|
||||||
+ " moderated, moderator_role, announce_message, announce_message_pl, announce_ping "
|
|
||||||
+ $"from {BackingTable} where guild_id = @Gid";
|
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
|
||||||
c.Prepare();
|
|
||||||
using var r = await c.ExecuteReaderAsync().ConfigureAwait(false);
|
|
||||||
if (await r.ReadAsync().ConfigureAwait(false)) return new GuildConfiguration(r);
|
|
||||||
}
|
|
||||||
if (nullIfUnknown) return null;
|
|
||||||
|
|
||||||
// If we got here, no row exists. Create it with default values.
|
|
||||||
using (var c = db.CreateCommand())
|
|
||||||
{
|
|
||||||
c.CommandText = $"insert into {BackingTable} (guild_id) values (@Gid)";
|
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
|
||||||
c.Prepare();
|
|
||||||
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// With a new row created, try this again
|
|
||||||
return await LoadAsync(guildId, nullIfUnknown).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates values on the backing database with values from this object instance.
|
|
||||||
/// </summary>
|
|
||||||
public async Task UpdateAsync()
|
|
||||||
{
|
|
||||||
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
|
||||||
using var c = db.CreateCommand();
|
|
||||||
c.CommandText = $"update {BackingTable} set "
|
|
||||||
+ "role_id = @RoleId, "
|
|
||||||
+ "channel_announce_id = @ChannelId, "
|
|
||||||
+ "time_zone = @TimeZone, "
|
|
||||||
+ "moderated = @Moderated, "
|
|
||||||
+ "moderator_role = @ModRole, "
|
|
||||||
+ "announce_message = @AnnounceMsg, "
|
|
||||||
+ "announce_message_pl = @AnnounceMsgPl, "
|
|
||||||
+ "announce_ping = @AnnouncePing "
|
|
||||||
+ "where guild_id = @Gid";
|
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
|
||||||
NpgsqlParameter p;
|
|
||||||
|
|
||||||
p = c.Parameters.Add("@RoleId", NpgsqlDbType.Bigint);
|
|
||||||
if (RoleId.HasValue) p.Value = (long)RoleId.Value;
|
|
||||||
else p.Value = DBNull.Value;
|
|
||||||
|
|
||||||
p = c.Parameters.Add("@ChannelId", NpgsqlDbType.Bigint);
|
|
||||||
if (AnnounceChannelId.HasValue) p.Value = (long)AnnounceChannelId.Value;
|
|
||||||
else p.Value = DBNull.Value;
|
|
||||||
|
|
||||||
p = c.Parameters.Add("@TimeZone", NpgsqlDbType.Text);
|
|
||||||
if (TimeZone != null) p.Value = TimeZone;
|
|
||||||
else p.Value = DBNull.Value;
|
|
||||||
|
|
||||||
c.Parameters.Add("@Moderated", NpgsqlDbType.Boolean).Value = IsModerated;
|
|
||||||
|
|
||||||
p = c.Parameters.Add("@ModRole", NpgsqlDbType.Bigint);
|
|
||||||
if (ModeratorRole.HasValue) p.Value = (long)ModeratorRole.Value;
|
|
||||||
else p.Value = DBNull.Value;
|
|
||||||
|
|
||||||
p = c.Parameters.Add("@AnnounceMsg", NpgsqlDbType.Text);
|
|
||||||
if (AnnounceMessages.Item1 != null) p.Value = AnnounceMessages.Item1;
|
|
||||||
else p.Value = DBNull.Value;
|
|
||||||
|
|
||||||
p = c.Parameters.Add("@AnnounceMsgPl", NpgsqlDbType.Text);
|
|
||||||
if (AnnounceMessages.Item2 != null) p.Value = AnnounceMessages.Item2;
|
|
||||||
else p.Value = DBNull.Value;
|
|
||||||
|
|
||||||
c.Parameters.Add("@AnnouncePing", NpgsqlDbType.Boolean).Value = AnnouncePing;
|
|
||||||
|
|
||||||
c.Prepare();
|
|
||||||
c.ExecuteNonQuery();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches guild settings from the database. If no corresponding entry exists, it will be created.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nullIfUnknown">
|
||||||
|
/// If true, this method shall not create a new entry and will return null if the guild does
|
||||||
|
/// not exist in the database.
|
||||||
|
/// </param>
|
||||||
|
public static async Task<GuildConfiguration> LoadAsync(ulong guildId, bool nullIfUnknown) {
|
||||||
|
using (var db = await Database.OpenConnectionAsync().ConfigureAwait(false)) {
|
||||||
|
using (var c = db.CreateCommand()) {
|
||||||
|
// Take note of ordinals for the constructor
|
||||||
|
c.CommandText = "select guild_id, role_id, channel_announce_id, time_zone, "
|
||||||
|
+ " moderated, moderator_role, announce_message, announce_message_pl, announce_ping "
|
||||||
|
+ $"from {BackingTable} where guild_id = @Gid";
|
||||||
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
||||||
|
c.Prepare();
|
||||||
|
using var r = await c.ExecuteReaderAsync().ConfigureAwait(false);
|
||||||
|
if (await r.ReadAsync().ConfigureAwait(false)) return new GuildConfiguration(r);
|
||||||
|
}
|
||||||
|
if (nullIfUnknown) return null;
|
||||||
|
|
||||||
|
// If we got here, no row exists. Create it with default values.
|
||||||
|
using (var c = db.CreateCommand()) {
|
||||||
|
c.CommandText = $"insert into {BackingTable} (guild_id) values (@Gid)";
|
||||||
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
||||||
|
c.Prepare();
|
||||||
|
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// With a new row created, try this again
|
||||||
|
return await LoadAsync(guildId, nullIfUnknown).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates values on the backing database with values from this object instance.
|
||||||
|
/// </summary>
|
||||||
|
public async Task UpdateAsync() {
|
||||||
|
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
||||||
|
using var c = db.CreateCommand();
|
||||||
|
c.CommandText = $"update {BackingTable} set "
|
||||||
|
+ "role_id = @RoleId, "
|
||||||
|
+ "channel_announce_id = @ChannelId, "
|
||||||
|
+ "time_zone = @TimeZone, "
|
||||||
|
+ "moderated = @Moderated, "
|
||||||
|
+ "moderator_role = @ModRole, "
|
||||||
|
+ "announce_message = @AnnounceMsg, "
|
||||||
|
+ "announce_message_pl = @AnnounceMsgPl, "
|
||||||
|
+ "announce_ping = @AnnouncePing "
|
||||||
|
+ "where guild_id = @Gid";
|
||||||
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
||||||
|
NpgsqlParameter p;
|
||||||
|
|
||||||
|
p = c.Parameters.Add("@RoleId", NpgsqlDbType.Bigint);
|
||||||
|
if (RoleId.HasValue) p.Value = (long)RoleId.Value;
|
||||||
|
else p.Value = DBNull.Value;
|
||||||
|
|
||||||
|
p = c.Parameters.Add("@ChannelId", NpgsqlDbType.Bigint);
|
||||||
|
if (AnnounceChannelId.HasValue) p.Value = (long)AnnounceChannelId.Value;
|
||||||
|
else p.Value = DBNull.Value;
|
||||||
|
|
||||||
|
p = c.Parameters.Add("@TimeZone", NpgsqlDbType.Text);
|
||||||
|
if (TimeZone != null) p.Value = TimeZone;
|
||||||
|
else p.Value = DBNull.Value;
|
||||||
|
|
||||||
|
c.Parameters.Add("@Moderated", NpgsqlDbType.Boolean).Value = IsModerated;
|
||||||
|
|
||||||
|
p = c.Parameters.Add("@ModRole", NpgsqlDbType.Bigint);
|
||||||
|
if (ModeratorRole.HasValue) p.Value = (long)ModeratorRole.Value;
|
||||||
|
else p.Value = DBNull.Value;
|
||||||
|
|
||||||
|
p = c.Parameters.Add("@AnnounceMsg", NpgsqlDbType.Text);
|
||||||
|
if (AnnounceMessages.Item1 != null) p.Value = AnnounceMessages.Item1;
|
||||||
|
else p.Value = DBNull.Value;
|
||||||
|
|
||||||
|
p = c.Parameters.Add("@AnnounceMsgPl", NpgsqlDbType.Text);
|
||||||
|
if (AnnounceMessages.Item2 != null) p.Value = AnnounceMessages.Item2;
|
||||||
|
else p.Value = DBNull.Value;
|
||||||
|
|
||||||
|
c.Parameters.Add("@AnnouncePing", NpgsqlDbType.Boolean).Value = AnnouncePing;
|
||||||
|
|
||||||
|
c.Prepare();
|
||||||
|
c.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,147 +5,137 @@ using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BirthdayBot.Data
|
namespace BirthdayBot.Data;
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents configuration for a guild user as may exist in the database.
|
||||||
|
/// </summary>
|
||||||
|
class GuildUserConfiguration {
|
||||||
|
public ulong GuildId { get; }
|
||||||
|
public ulong UserId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents configuration for a guild user.
|
/// Month of birth as a numeric value. Range 1-12.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class GuildUserConfiguration
|
public int BirthMonth { get; private set; }
|
||||||
{
|
/// <summary>
|
||||||
public ulong GuildId { get; }
|
/// Day of birth as a numeric value. Ranges between 1-31 or lower based on month value.
|
||||||
public ulong UserId { get; }
|
/// </summary>
|
||||||
|
public int BirthDay { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
public string TimeZone { get; private set; }
|
||||||
/// Month of birth as a numeric value. Range 1-12.
|
public bool IsKnown { get { return BirthMonth != 0 && BirthDay != 0; } }
|
||||||
/// </summary>
|
|
||||||
public int BirthMonth { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Day of birth as a numeric value. Ranges between 1-31 or lower based on month value.
|
|
||||||
/// </summary>
|
|
||||||
public int BirthDay { get; private set; }
|
|
||||||
|
|
||||||
public string TimeZone { get; private set; }
|
/// <summary>
|
||||||
public bool IsKnown { get { return BirthMonth != 0 && BirthDay != 0; } }
|
/// Creates a new, data-less instance without a corresponding database entry.
|
||||||
|
/// Calling <see cref="UpdateAsync(int, int, int)"/> will create a real database enty
|
||||||
|
/// </summary>
|
||||||
|
private GuildUserConfiguration(ulong guildId, ulong userId) {
|
||||||
|
GuildId = guildId;
|
||||||
|
UserId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Called by GetGuildUsersAsync. Double-check ordinals when changes are made.
|
||||||
/// Creates a new, data-less instance without a corresponding database entry.
|
private GuildUserConfiguration(DbDataReader reader) {
|
||||||
/// Calling <see cref="UpdateAsync(int, int, int)"/> will create a real database enty
|
GuildId = (ulong)reader.GetInt64(0);
|
||||||
/// </summary>
|
UserId = (ulong)reader.GetInt64(1);
|
||||||
private GuildUserConfiguration(ulong guildId, ulong userId)
|
BirthMonth = reader.GetInt32(2);
|
||||||
{
|
BirthDay = reader.GetInt32(3);
|
||||||
GuildId = guildId;
|
if (!reader.IsDBNull(4)) TimeZone = reader.GetString(4);
|
||||||
UserId = userId;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Called by GetGuildUsersAsync. Double-check ordinals when changes are made.
|
/// <summary>
|
||||||
private GuildUserConfiguration(DbDataReader reader)
|
/// Updates user with given information.
|
||||||
{
|
/// </summary>
|
||||||
GuildId = (ulong)reader.GetInt64(0);
|
public async Task UpdateAsync(int month, int day, string newtz) {
|
||||||
UserId = (ulong)reader.GetInt64(1);
|
using (var db = await Database.OpenConnectionAsync().ConfigureAwait(false)) {
|
||||||
BirthMonth = reader.GetInt32(2);
|
|
||||||
BirthDay = reader.GetInt32(3);
|
|
||||||
if (!reader.IsDBNull(4)) TimeZone = reader.GetString(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates user with given information.
|
|
||||||
/// </summary>
|
|
||||||
public async Task UpdateAsync(int month, int day, string newtz)
|
|
||||||
{
|
|
||||||
using (var db = await Database.OpenConnectionAsync().ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
using var c = db.CreateCommand();
|
|
||||||
c.CommandText = $"insert into {BackingTable} "
|
|
||||||
+ "(guild_id, user_id, birth_month, birth_day, time_zone) values "
|
|
||||||
+ "(@Gid, @Uid, @Month, @Day, @Tz) "
|
|
||||||
+ "on conflict (guild_id, user_id) do update "
|
|
||||||
+ "set birth_month = EXCLUDED.birth_month, birth_day = EXCLUDED.birth_day, time_zone = EXCLUDED.time_zone";
|
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
|
||||||
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)UserId;
|
|
||||||
c.Parameters.Add("@Month", NpgsqlDbType.Numeric).Value = month;
|
|
||||||
c.Parameters.Add("@Day", NpgsqlDbType.Numeric).Value = day;
|
|
||||||
var tzp = c.Parameters.Add("@Tz", NpgsqlDbType.Text);
|
|
||||||
if (newtz != null) tzp.Value = newtz;
|
|
||||||
else tzp.Value = DBNull.Value;
|
|
||||||
c.Prepare();
|
|
||||||
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database update succeeded; update instance values
|
|
||||||
BirthMonth = month;
|
|
||||||
BirthDay = day;
|
|
||||||
TimeZone = newtz;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deletes information of this user from the backing database.
|
|
||||||
/// The corresponding object reference should ideally be discarded after calling this.
|
|
||||||
/// </summary>
|
|
||||||
public async Task DeleteAsync()
|
|
||||||
{
|
|
||||||
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
|
||||||
using var c = db.CreateCommand();
|
using var c = db.CreateCommand();
|
||||||
c.CommandText = $"delete from {BackingTable} "
|
c.CommandText = $"insert into {BackingTable} "
|
||||||
+ "where guild_id = @Gid and user_id = @Uid";
|
+ "(guild_id, user_id, birth_month, birth_day, time_zone) values "
|
||||||
|
+ "(@Gid, @Uid, @Month, @Day, @Tz) "
|
||||||
|
+ "on conflict (guild_id, user_id) do update "
|
||||||
|
+ "set birth_month = EXCLUDED.birth_month, birth_day = EXCLUDED.birth_day, time_zone = EXCLUDED.time_zone";
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
||||||
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)UserId;
|
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)UserId;
|
||||||
|
c.Parameters.Add("@Month", NpgsqlDbType.Numeric).Value = month;
|
||||||
|
c.Parameters.Add("@Day", NpgsqlDbType.Numeric).Value = day;
|
||||||
|
var tzp = c.Parameters.Add("@Tz", NpgsqlDbType.Text);
|
||||||
|
if (newtz != null) tzp.Value = newtz;
|
||||||
|
else tzp.Value = DBNull.Value;
|
||||||
c.Prepare();
|
c.Prepare();
|
||||||
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Database
|
// Database update succeeded; update instance values
|
||||||
public const string BackingTable = "user_birthdays";
|
BirthMonth = month;
|
||||||
// Take note of ordinals for use in the constructor
|
BirthDay = day;
|
||||||
private const string SelectFields = "guild_id, user_id, birth_month, birth_day, time_zone";
|
TimeZone = newtz;
|
||||||
|
|
||||||
internal static async Task DatabaseSetupAsync(NpgsqlConnection db)
|
|
||||||
{
|
|
||||||
using var c = db.CreateCommand();
|
|
||||||
c.CommandText = $"create table if not exists {BackingTable} ("
|
|
||||||
+ $"guild_id bigint not null references {GuildConfiguration.BackingTable} ON DELETE CASCADE, "
|
|
||||||
+ "user_id bigint not null, "
|
|
||||||
+ "birth_month integer not null, "
|
|
||||||
+ "birth_day integer not null, "
|
|
||||||
+ "time_zone text null, "
|
|
||||||
+ "last_seen timestamptz not null default NOW(), "
|
|
||||||
+ "PRIMARY KEY (guild_id, user_id)" // index automatically created with this
|
|
||||||
+ ")";
|
|
||||||
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to retrieve a user's configuration. Returns a new, updateable instance if none is found.
|
|
||||||
/// </summary>
|
|
||||||
public static async Task<GuildUserConfiguration> LoadAsync(ulong guildId, ulong userId)
|
|
||||||
{
|
|
||||||
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
|
||||||
using var c = db.CreateCommand();
|
|
||||||
c.CommandText = $"select {SelectFields} from {BackingTable} where guild_id = @Gid and user_id = @Uid";
|
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
|
||||||
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
|
||||||
c.Prepare();
|
|
||||||
|
|
||||||
using var r = c.ExecuteReader();
|
|
||||||
if (await r.ReadAsync().ConfigureAwait(false)) return new GuildUserConfiguration(r);
|
|
||||||
else return new GuildUserConfiguration(guildId, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all known user configuration records associated with the specified guild.
|
|
||||||
/// </summary>
|
|
||||||
public static async Task<IEnumerable<GuildUserConfiguration>> LoadAllAsync(ulong guildId)
|
|
||||||
{
|
|
||||||
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
|
||||||
using var c = db.CreateCommand();
|
|
||||||
c.CommandText = $"select {SelectFields} from {BackingTable} where guild_id = @Gid";
|
|
||||||
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
|
||||||
c.Prepare();
|
|
||||||
|
|
||||||
using var r = await c.ExecuteReaderAsync().ConfigureAwait(false);
|
|
||||||
var result = new List<GuildUserConfiguration>();
|
|
||||||
while (await r.ReadAsync().ConfigureAwait(false)) result.Add(new GuildUserConfiguration(r));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes information of this user from the backing database.
|
||||||
|
/// The corresponding object reference should ideally be discarded after calling this.
|
||||||
|
/// </summary>
|
||||||
|
public async Task DeleteAsync() {
|
||||||
|
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
||||||
|
using var c = db.CreateCommand();
|
||||||
|
c.CommandText = $"delete from {BackingTable} "
|
||||||
|
+ "where guild_id = @Gid and user_id = @Uid";
|
||||||
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)GuildId;
|
||||||
|
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)UserId;
|
||||||
|
c.Prepare();
|
||||||
|
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Database
|
||||||
|
public const string BackingTable = "user_birthdays";
|
||||||
|
// Take note of ordinals for use in the constructor
|
||||||
|
private const string SelectFields = "guild_id, user_id, birth_month, birth_day, time_zone";
|
||||||
|
|
||||||
|
internal static async Task DatabaseSetupAsync(NpgsqlConnection db) {
|
||||||
|
using var c = db.CreateCommand();
|
||||||
|
c.CommandText = $"create table if not exists {BackingTable} ("
|
||||||
|
+ $"guild_id bigint not null references {GuildConfiguration.BackingTable} ON DELETE CASCADE, "
|
||||||
|
+ "user_id bigint not null, "
|
||||||
|
+ "birth_month integer not null, "
|
||||||
|
+ "birth_day integer not null, "
|
||||||
|
+ "time_zone text null, "
|
||||||
|
+ "last_seen timestamptz not null default NOW(), "
|
||||||
|
+ "PRIMARY KEY (guild_id, user_id)" // index automatically created with this
|
||||||
|
+ ")";
|
||||||
|
await c.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve a user's configuration. Returns a new, updateable instance if none is found.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<GuildUserConfiguration> LoadAsync(ulong guildId, ulong userId) {
|
||||||
|
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
||||||
|
using var c = db.CreateCommand();
|
||||||
|
c.CommandText = $"select {SelectFields} from {BackingTable} where guild_id = @Gid and user_id = @Uid";
|
||||||
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
||||||
|
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = (long)userId;
|
||||||
|
c.Prepare();
|
||||||
|
|
||||||
|
using var r = c.ExecuteReader();
|
||||||
|
if (await r.ReadAsync().ConfigureAwait(false)) return new GuildUserConfiguration(r);
|
||||||
|
else return new GuildUserConfiguration(guildId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all known user configuration records associated with the specified guild.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<IEnumerable<GuildUserConfiguration>> LoadAllAsync(ulong guildId) {
|
||||||
|
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
|
||||||
|
using var c = db.CreateCommand();
|
||||||
|
c.CommandText = $"select {SelectFields} from {BackingTable} where guild_id = @Gid";
|
||||||
|
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = (long)guildId;
|
||||||
|
c.Prepare();
|
||||||
|
|
||||||
|
using var r = await c.ExecuteReaderAsync().ConfigureAwait(false);
|
||||||
|
var result = new List<GuildUserConfiguration>();
|
||||||
|
while (await r.ReadAsync().ConfigureAwait(false)) result.Add(new GuildUserConfiguration(r));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue