Fix several database issues

- Specify default value
- Cache all guild users on initial download
- Fix foreign key constraint issues
This commit is contained in:
Noi 2022-06-10 16:09:18 -07:00
parent da31ce3e0d
commit 6afa6dc2c6
6 changed files with 36 additions and 16 deletions

View file

@ -33,6 +33,7 @@ public class BotDatabaseContext : DbContext {
modelBuilder.Entity<CachedGuildUser>(entity => {
entity.Navigation(e => e.User).AutoInclude();
entity.HasKey(e => new { e.UserId, e.GuildId });
entity.Property(e => e.FirstSeenTime).HasDefaultValueSql("now()");
});
modelBuilder.Entity<CachedGuildMessage>(entity => entity.Property(e => e.CreatedAt).HasDefaultValueSql("now()"));
}

View file

@ -13,8 +13,8 @@ using RegexBot.Data;
namespace RegexBot.Data.Migrations
{
[DbContext(typeof(BotDatabaseContext))]
[Migration("20220513061851_InitialEFSetup")]
partial class InitialEFSetup
[Migration("20220610210059_InitialMigration")]
partial class InitialMigration
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
@ -83,8 +83,10 @@ namespace RegexBot.Data.Migrations
.HasColumnName("guild_id");
b.Property<DateTimeOffset>("FirstSeenTime")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("first_seen_time");
.HasColumnName("first_seen_time")
.HasDefaultValueSql("now()");
b.Property<DateTimeOffset>("GULastUpdateTime")
.HasColumnType("timestamp with time zone")

View file

@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace RegexBot.Data.Migrations
{
public partial class InitialEFSetup : Migration
public partial class InitialMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
@ -73,7 +73,7 @@ namespace RegexBot.Data.Migrations
user_id = table.Column<long>(type: "bigint", nullable: false),
guild_id = table.Column<long>(type: "bigint", nullable: false),
gu_last_update_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
first_seen_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
first_seen_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
nickname = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>

View file

@ -81,8 +81,10 @@ namespace RegexBot.Data.Migrations
.HasColumnName("guild_id");
b.Property<DateTimeOffset>("FirstSeenTime")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("first_seen_time");
.HasColumnName("first_seen_time")
.HasDefaultValueSql("now()");
b.Property<DateTimeOffset>("GULastUpdateTime")
.HasColumnType("timestamp with time zone")

View file

@ -29,7 +29,8 @@ class Program {
MessageCacheSize = 0, // using our own
LogLevel = LogSeverity.Info,
GatewayIntents = GatewayIntents.All & ~GatewayIntents.GuildPresences,
LogGatewayIntentWarnings = false
LogGatewayIntentWarnings = false,
AlwaysDownloadUsers = true
});
// Kerobot class initialization - will set up services and modules

View file

@ -13,16 +13,35 @@ class UserCachingSubservice {
private static Regex DiscriminatorSearch { get; } = new(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled);
internal UserCachingSubservice(RegexbotClient bot) {
bot.DiscordClient.GuildMembersDownloaded += DiscordClient_GuildMembersDownloaded;
bot.DiscordClient.GuildMemberUpdated += DiscordClient_GuildMemberUpdated;
bot.DiscordClient.UserUpdated += DiscordClient_UserUpdated;
}
private async Task DiscordClient_GuildMembersDownloaded(SocketGuild arg) {
using var db = new BotDatabaseContext();
foreach (var user in arg.Users) {
UpdateUser(user, db);
UpdateGuildUser(user, db);
}
await db.SaveChangesAsync();
}
private async Task DiscordClient_GuildMemberUpdated(Discord.Cacheable<SocketGuildUser, ulong> old, SocketGuildUser current) {
using var db = new BotDatabaseContext();
UpdateUser(current, db); // Update user data first (avoid potential foreign key constraint violation)
UpdateGuildUser(current, db);
await db.SaveChangesAsync();
}
private async Task DiscordClient_UserUpdated(SocketUser old, SocketUser current) {
using var db = new BotDatabaseContext();
UpdateUser(current, db);
await db.SaveChangesAsync();
}
// IMPORTANT: Do NOT forget to save changes in database after calling this!
private static void UpdateUser(SocketUser user, BotDatabaseContext db) {
CachedUser uinfo;
try {
@ -38,23 +57,18 @@ class UserCachingSubservice {
uinfo.ULastUpdateTime = DateTimeOffset.UtcNow;
}
private async Task DiscordClient_GuildMemberUpdated(Discord.Cacheable<SocketGuildUser, ulong> old, SocketGuildUser current) {
using var db = new BotDatabaseContext();
UpdateUser(current, db); // Update user data too (avoid potential foreign key constraint violation)
private static void UpdateGuildUser(SocketGuildUser user, BotDatabaseContext db) {
CachedGuildUser guinfo;
try {
guinfo = db.GuildUserCache.Where(c => c.GuildId == (long)current.Guild.Id && c.UserId == (long)current.Id).First();
guinfo = db.GuildUserCache.Where(c => c.GuildId == (long)user.Guild.Id && c.UserId == (long)user.Id).First();
} catch (InvalidOperationException) {
guinfo = new() { GuildId = (long)current.Guild.Id, UserId = (long)current.Id };
guinfo = new() { GuildId = (long)user.Guild.Id, UserId = (long)user.Id };
db.GuildUserCache.Add(guinfo);
}
guinfo.GULastUpdateTime = DateTimeOffset.UtcNow;
guinfo.Nickname = current.Nickname;
guinfo.Nickname = user.Nickname;
// TODO guild-specific avatar, other details?
await db.SaveChangesAsync();
}
// Hooked