feat: exorcise entity framework core from most responders
This commit is contained in:
parent
33b78a7ac5
commit
5891f28f7c
32 changed files with 743 additions and 145 deletions
18
.idea/.idea.catalogger/.idea/dataSources.xml
generated
Normal file
18
.idea/.idea.catalogger/.idea/dataSources.xml
generated
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="catalogger@localhost" uuid="39716cd5-9fcb-47a3-a5e8-23a6347da401">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://localhost:5432/catalogger</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.resource.type" value="Deployment" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.catalogger/.idea/sqldialects.xml
generated
Normal file
6
.idea/.idea.catalogger/.idea/sqldialects.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="PROJECT" dialect="PostgreSQL" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -14,8 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
|
|
@ -27,7 +26,7 @@ using Remora.Results;
|
||||||
namespace Catalogger.Backend.Bot.Responders.Channels;
|
namespace Catalogger.Backend.Bot.Responders.Channels;
|
||||||
|
|
||||||
public class ChannelCreateResponder(
|
public class ChannelCreateResponder(
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
|
|
@ -96,7 +95,7 @@ public class ChannelCreateResponder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(ch.GuildID.Value, false, ct);
|
var guildConfig = await guildRepository.GetAsync(ch.GuildID);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.ChannelCreate,
|
LogChannelType.ChannelCreate,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -27,7 +28,7 @@ namespace Catalogger.Backend.Bot.Responders.Channels;
|
||||||
|
|
||||||
public class ChannelDeleteResponder(
|
public class ChannelDeleteResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
) : IResponder<IChannelDelete>
|
) : IResponder<IChannelDelete>
|
||||||
|
|
@ -49,7 +50,7 @@ public class ChannelDeleteResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID.Value, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID.Value);
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithTitle("Channel deleted")
|
.WithTitle("Channel deleted")
|
||||||
.WithColour(DiscordUtils.Red)
|
.WithColour(DiscordUtils.Red)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -30,7 +31,7 @@ namespace Catalogger.Backend.Bot.Responders.Channels;
|
||||||
|
|
||||||
public class ChannelUpdateResponder(
|
public class ChannelUpdateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
|
|
@ -49,7 +50,7 @@ public class ChannelUpdateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID.Value, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
|
|
||||||
var builder = new EmbedBuilder()
|
var builder = new EmbedBuilder()
|
||||||
.WithTitle(
|
.WithTitle(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -27,7 +28,7 @@ namespace Catalogger.Backend.Bot.Responders.Guilds;
|
||||||
|
|
||||||
public class GuildBanAddResponder(
|
public class GuildBanAddResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
WebhookExecutorService webhookExecutor,
|
WebhookExecutorService webhookExecutor,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
AuditLogCache auditLogCache,
|
AuditLogCache auditLogCache,
|
||||||
|
|
@ -38,7 +39,7 @@ public class GuildBanAddResponder(
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IGuildBanAdd evt, CancellationToken ct = default)
|
public async Task<Result> RespondAsync(IGuildBanAdd evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, true, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
|
|
||||||
// Delay 2 seconds for the audit log
|
// Delay 2 seconds for the audit log
|
||||||
await Task.Delay(2000, ct);
|
await Task.Delay(2000, ct);
|
||||||
|
|
@ -76,10 +77,8 @@ public class GuildBanAddResponder(
|
||||||
pkSystem.Id,
|
pkSystem.Id,
|
||||||
evt.GuildID
|
evt.GuildID
|
||||||
);
|
);
|
||||||
guildConfig.BannedSystems.Add(pkSystem.Id);
|
|
||||||
guildConfig.BannedSystems.Add(pkSystem.Uuid.ToString());
|
await guildRepository.BanSystemAsync(evt.GuildID, pkSystem.Id, pkSystem.Uuid);
|
||||||
db.Update(guildConfig);
|
|
||||||
await db.SaveChangesAsync(ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
embed.AddField(
|
embed.AddField(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -27,7 +28,7 @@ namespace Catalogger.Backend.Bot.Responders.Guilds;
|
||||||
|
|
||||||
public class GuildBanRemoveResponder(
|
public class GuildBanRemoveResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
WebhookExecutorService webhookExecutor,
|
WebhookExecutorService webhookExecutor,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
AuditLogCache auditLogCache,
|
AuditLogCache auditLogCache,
|
||||||
|
|
@ -38,7 +39,7 @@ public class GuildBanRemoveResponder(
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IGuildBanRemove evt, CancellationToken ct = default)
|
public async Task<Result> RespondAsync(IGuildBanRemove evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
|
|
||||||
// Delay 2 seconds for the audit log
|
// Delay 2 seconds for the audit log
|
||||||
await Task.Delay(2000, ct);
|
await Task.Delay(2000, ct);
|
||||||
|
|
@ -68,10 +69,7 @@ public class GuildBanRemoveResponder(
|
||||||
var pkSystem = await pluralkitApi.GetPluralKitSystemAsync(evt.User.ID.Value, ct);
|
var pkSystem = await pluralkitApi.GetPluralKitSystemAsync(evt.User.ID.Value, ct);
|
||||||
if (pkSystem != null)
|
if (pkSystem != null)
|
||||||
{
|
{
|
||||||
guildConfig.BannedSystems.Remove(pkSystem.Id);
|
await guildRepository.UnbanSystemAsync(evt.GuildID, pkSystem.Id, pkSystem.Uuid);
|
||||||
guildConfig.BannedSystems.Remove(pkSystem.Uuid.ToString());
|
|
||||||
db.Update(guildConfig);
|
|
||||||
await db.SaveChangesAsync(ct);
|
|
||||||
|
|
||||||
embed.AddField(
|
embed.AddField(
|
||||||
"PluralKit system",
|
"PluralKit system",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
|
|
@ -29,7 +30,7 @@ namespace Catalogger.Backend.Bot.Responders.Guilds;
|
||||||
public class GuildCreateResponder(
|
public class GuildCreateResponder(
|
||||||
Config config,
|
Config config,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
EmojiCache emojiCache,
|
EmojiCache emojiCache,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
|
|
@ -75,13 +76,9 @@ public class GuildCreateResponder(
|
||||||
guildId = unavailableGuild.ID.ToUlong();
|
guildId = unavailableGuild.ID.ToUlong();
|
||||||
}
|
}
|
||||||
|
|
||||||
var tx = await db.Database.BeginTransactionAsync(ct);
|
if (await guildRepository.IsGuildKnown(guildId))
|
||||||
if (await db.Guilds.FindAsync([guildId], ct) != null)
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
await guildRepository.AddGuildAsync(guildId);
|
||||||
db.Add(new Guild { Id = guildId });
|
|
||||||
await db.SaveChangesAsync(ct);
|
|
||||||
await tx.CommitAsync(ct);
|
|
||||||
|
|
||||||
_logger.Information("Joined new guild {GuildName} / {GuildId}", guildName, guildId);
|
_logger.Information("Joined new guild {GuildName} / {GuildId}", guildName, guildId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -29,7 +30,7 @@ namespace Catalogger.Backend.Bot.Responders.Guilds;
|
||||||
|
|
||||||
public class GuildEmojisUpdateResponder(
|
public class GuildEmojisUpdateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
EmojiCache emojiCache,
|
EmojiCache emojiCache,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
) : IResponder<IGuildEmojisUpdate>
|
) : IResponder<IGuildEmojisUpdate>
|
||||||
|
|
@ -111,7 +112,7 @@ public class GuildEmojisUpdateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildEmojisUpdate, embed);
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildEmojisUpdate, embed);
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -28,7 +29,7 @@ namespace Catalogger.Backend.Bot.Responders.Guilds;
|
||||||
|
|
||||||
public class GuildUpdateResponder(
|
public class GuildUpdateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
|
|
@ -97,7 +98,7 @@ public class GuildUpdateResponder(
|
||||||
|
|
||||||
if (embed.Fields.Count != 0)
|
if (embed.Fields.Count != 0)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(evt.ID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.ID);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildUpdate,
|
LogChannelType.GuildUpdate,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -28,7 +29,7 @@ namespace Catalogger.Backend.Bot.Responders.Invites;
|
||||||
|
|
||||||
public class InviteCreateResponder(
|
public class InviteCreateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
IInviteCache inviteCache,
|
IInviteCache inviteCache,
|
||||||
IDiscordRestGuildAPI guildApi,
|
IDiscordRestGuildAPI guildApi,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
|
|
@ -74,7 +75,7 @@ public class InviteCreateResponder(
|
||||||
inline: true
|
inline: true
|
||||||
);
|
);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.InviteCreate,
|
LogChannelType.InviteCreate,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -29,6 +31,7 @@ namespace Catalogger.Backend.Bot.Responders.Invites;
|
||||||
|
|
||||||
public class InviteDeleteResponder(
|
public class InviteDeleteResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
GuildRepository guildRepository,
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
IInviteCache inviteCache,
|
IInviteCache inviteCache,
|
||||||
WebhookExecutorService webhookExecutor,
|
WebhookExecutorService webhookExecutor,
|
||||||
|
|
@ -89,7 +92,7 @@ public class InviteDeleteResponder(
|
||||||
inline: true
|
inline: true
|
||||||
);
|
);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId, false, ct);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.InviteDelete,
|
LogChannelType.InviteDelete,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -35,6 +36,7 @@ namespace Catalogger.Backend.Bot.Responders.Members;
|
||||||
public class GuildMemberAddResponder(
|
public class GuildMemberAddResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
|
GuildRepository guildRepository,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
IInviteCache inviteCache,
|
IInviteCache inviteCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
|
|
@ -62,7 +64,7 @@ public class GuildMemberAddResponder(
|
||||||
.WithCurrentTimestamp()
|
.WithCurrentTimestamp()
|
||||||
.WithFooter($"ID: {user.ID}");
|
.WithFooter($"ID: {user.ID}");
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(member.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(member.GuildID);
|
||||||
var guildRes = await guildApi.GetGuildAsync(member.GuildID, withCounts: true, ct);
|
var guildRes = await guildApi.GetGuildAsync(member.GuildID, withCounts: true, ct);
|
||||||
if (guildRes.IsSuccess && guildRes.Entity.ApproximateMemberCount.IsDefined())
|
if (guildRes.IsSuccess && guildRes.Entity.ApproximateMemberCount.IsDefined())
|
||||||
builder.Description +=
|
builder.Description +=
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
|
|
@ -28,7 +27,7 @@ namespace Catalogger.Backend.Bot.Responders.Members;
|
||||||
|
|
||||||
public class GuildMemberRemoveResponder(
|
public class GuildMemberRemoveResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
|
|
@ -50,7 +49,7 @@ public class GuildMemberRemoveResponder(
|
||||||
.WithFooter($"ID: {evt.User.ID}")
|
.WithFooter($"ID: {evt.User.ID}")
|
||||||
.WithCurrentTimestamp();
|
.WithCurrentTimestamp();
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
|
|
||||||
var member = await memberCache.TryGetAsync(evt.GuildID, evt.User.ID);
|
var member = await memberCache.TryGetAsync(evt.GuildID, evt.User.ID);
|
||||||
if (member == null)
|
if (member == null)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -30,7 +31,7 @@ namespace Catalogger.Backend.Bot.Responders.Members;
|
||||||
|
|
||||||
public class GuildMemberUpdateResponder(
|
public class GuildMemberUpdateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
|
|
@ -145,7 +146,7 @@ public class GuildMemberUpdateResponder(
|
||||||
.GetOrThrow();
|
.GetOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(newMember.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(newMember.GuildID);
|
||||||
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildMemberAvatarUpdate, embed);
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildMemberAvatarUpdate, embed);
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +205,7 @@ public class GuildMemberUpdateResponder(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(newMember.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(newMember.GuildID);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildMemberNickUpdate,
|
LogChannelType.GuildMemberNickUpdate,
|
||||||
|
|
@ -253,7 +254,7 @@ public class GuildMemberUpdateResponder(
|
||||||
embed.AddField("Reason", "*(unknown)*");
|
embed.AddField("Reason", "*(unknown)*");
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(member.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(member.GuildID);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildMemberTimeout,
|
LogChannelType.GuildMemberTimeout,
|
||||||
|
|
@ -268,7 +269,7 @@ public class GuildMemberUpdateResponder(
|
||||||
CancellationToken ct = default
|
CancellationToken ct = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(member.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(member.GuildID);
|
||||||
var guildRoles = roleCache.GuildRoles(member.GuildID).ToList();
|
var guildRoles = roleCache.GuildRoles(member.GuildID).ToList();
|
||||||
|
|
||||||
var keyRoleUpdate = new EmbedBuilder()
|
var keyRoleUpdate = new EmbedBuilder()
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@
|
||||||
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Models;
|
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
|
@ -30,8 +28,8 @@ namespace Catalogger.Backend.Bot.Responders.Messages;
|
||||||
public class MessageCreateResponder(
|
public class MessageCreateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
Config config,
|
Config config,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
MessageRepository messageRepository,
|
DapperMessageRepository messageRepository,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
PkMessageHandler pkMessageHandler
|
PkMessageHandler pkMessageHandler
|
||||||
) : IResponder<IMessageCreate>
|
) : IResponder<IMessageCreate>
|
||||||
|
|
@ -52,13 +50,12 @@ public class MessageCreateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guild = await db.GetGuildAsync(msg.GuildID, false, ct);
|
var guild = await guildRepository.GetAsync(msg.GuildID);
|
||||||
// The guild needs to have enabled at least one of the message logging events,
|
// The guild needs to have enabled at least one of the message logging events,
|
||||||
// and the channel must not be ignored, to store the message.
|
// and the channel must not be ignored, to store the message.
|
||||||
if (guild.IsMessageIgnored(msg.ChannelID, msg.Author.ID))
|
if (guild.IsMessageIgnored(msg.ChannelID, msg.Author.ID))
|
||||||
{
|
{
|
||||||
db.IgnoredMessages.Add(new IgnoredMessage(msg.ID.ToUlong()));
|
await messageRepository.IgnoreMessageAsync(msg.ID.Value);
|
||||||
await db.SaveChangesAsync(ct);
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,8 +65,7 @@ public class MessageCreateResponder(
|
||||||
_ = pkMessageHandler.HandleProxiedMessageAsync(msg.ID.Value);
|
_ = pkMessageHandler.HandleProxiedMessageAsync(msg.ID.Value);
|
||||||
else if (msg.ApplicationID.HasValue && msg.ApplicationID.Is(config.Discord.ApplicationId))
|
else if (msg.ApplicationID.HasValue && msg.ApplicationID.Is(config.Discord.ApplicationId))
|
||||||
{
|
{
|
||||||
db.IgnoredMessages.Add(new IgnoredMessage(msg.ID.Value));
|
await messageRepository.IgnoreMessageAsync(msg.ID.Value);
|
||||||
await db.SaveChangesAsync(ct);
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,19 +145,19 @@ public partial class PkMessageHandler(ILogger logger, IServiceProvider services)
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var scope = services.CreateAsyncScope();
|
await using var scope = services.CreateAsyncScope();
|
||||||
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
await using var messageRepository =
|
||||||
var messageRepository = scope.ServiceProvider.GetRequiredService<MessageRepository>();
|
scope.ServiceProvider.GetRequiredService<DapperMessageRepository>();
|
||||||
|
|
||||||
await messageRepository.SetProxiedMessageDataAsync(
|
await Task.WhenAll(
|
||||||
|
messageRepository.SetProxiedMessageDataAsync(
|
||||||
msgId,
|
msgId,
|
||||||
originalId,
|
originalId,
|
||||||
authorId,
|
authorId,
|
||||||
systemId: match.Groups[1].Value,
|
systemId: match.Groups[1].Value,
|
||||||
memberId: match.Groups[2].Value
|
memberId: match.Groups[2].Value
|
||||||
|
),
|
||||||
|
messageRepository.IgnoreMessageAsync(originalId)
|
||||||
);
|
);
|
||||||
|
|
||||||
db.IgnoredMessages.Add(new IgnoredMessage(originalId));
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HandleProxiedMessageAsync(ulong msgId)
|
public async Task HandleProxiedMessageAsync(ulong msgId)
|
||||||
|
|
@ -169,8 +165,8 @@ public partial class PkMessageHandler(ILogger logger, IServiceProvider services)
|
||||||
await Task.Delay(3.Seconds());
|
await Task.Delay(3.Seconds());
|
||||||
|
|
||||||
await using var scope = services.CreateAsyncScope();
|
await using var scope = services.CreateAsyncScope();
|
||||||
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
await using var messageRepository =
|
||||||
var messageRepository = scope.ServiceProvider.GetRequiredService<MessageRepository>();
|
scope.ServiceProvider.GetRequiredService<DapperMessageRepository>();
|
||||||
var pluralkitApi = scope.ServiceProvider.GetRequiredService<PluralkitApiService>();
|
var pluralkitApi = scope.ServiceProvider.GetRequiredService<PluralkitApiService>();
|
||||||
|
|
||||||
var (isStored, hasProxyInfo) = await messageRepository.HasProxyInfoAsync(msgId);
|
var (isStored, hasProxyInfo) = await messageRepository.HasProxyInfoAsync(msgId);
|
||||||
|
|
@ -193,15 +189,15 @@ public partial class PkMessageHandler(ILogger logger, IServiceProvider services)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await messageRepository.SetProxiedMessageDataAsync(
|
await Task.WhenAll(
|
||||||
|
messageRepository.SetProxiedMessageDataAsync(
|
||||||
msgId,
|
msgId,
|
||||||
pkMessage.Original,
|
pkMessage.Original,
|
||||||
pkMessage.Sender,
|
pkMessage.Sender,
|
||||||
pkMessage.System?.Id,
|
pkMessage.System?.Id,
|
||||||
pkMessage.Member?.Id
|
pkMessage.Member?.Id
|
||||||
|
),
|
||||||
|
messageRepository.IgnoreMessageAsync(pkMessage.Original)
|
||||||
);
|
);
|
||||||
|
|
||||||
db.IgnoredMessages.Add(new IgnoredMessage(pkMessage.Original));
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using NodaTime.Extensions;
|
using NodaTime.Extensions;
|
||||||
|
|
@ -32,8 +31,8 @@ namespace Catalogger.Backend.Bot.Responders.Messages;
|
||||||
|
|
||||||
public class MessageDeleteBulkResponder(
|
public class MessageDeleteBulkResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
MessageRepository messageRepository,
|
DapperMessageRepository messageRepository,
|
||||||
WebhookExecutorService webhookExecutor,
|
WebhookExecutorService webhookExecutor,
|
||||||
ChannelCache channelCache
|
ChannelCache channelCache
|
||||||
) : IResponder<IMessageDeleteBulk>
|
) : IResponder<IMessageDeleteBulk>
|
||||||
|
|
@ -42,7 +41,7 @@ public class MessageDeleteBulkResponder(
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IMessageDeleteBulk evt, CancellationToken ct = default)
|
public async Task<Result> RespondAsync(IMessageDeleteBulk evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var guild = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guild = await guildRepository.GetAsync(evt.GuildID);
|
||||||
if (guild.IsMessageIgnored(evt.ChannelID, null))
|
if (guild.IsMessageIgnored(evt.ChannelID, null))
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
|
|
@ -77,7 +76,7 @@ public class MessageDeleteBulkResponder(
|
||||||
|
|
||||||
foreach (var msgId in evt.IDs.Order())
|
foreach (var msgId in evt.IDs.Order())
|
||||||
{
|
{
|
||||||
if (await messageRepository.IsMessageIgnoredAsync(msgId.Value, ct))
|
if (await messageRepository.IsMessageIgnoredAsync(msgId.Value))
|
||||||
{
|
{
|
||||||
ignoredMessages++;
|
ignoredMessages++;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -129,7 +128,7 @@ public class MessageDeleteBulkResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string RenderMessage(Snowflake messageId, MessageRepository.Message? message)
|
private string RenderMessage(Snowflake messageId, DapperMessageRepository.Message? message)
|
||||||
{
|
{
|
||||||
var timestamp = messageId.Timestamp.ToOffsetDateTime().ToString();
|
var timestamp = messageId.Timestamp.ToOffsetDateTime().ToString();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
|
@ -33,8 +32,8 @@ namespace Catalogger.Backend.Bot.Responders.Messages;
|
||||||
|
|
||||||
public class MessageDeleteResponder(
|
public class MessageDeleteResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
MessageRepository messageRepository,
|
DapperMessageRepository messageRepository,
|
||||||
WebhookExecutorService webhookExecutor,
|
WebhookExecutorService webhookExecutor,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
|
|
@ -61,10 +60,10 @@ public class MessageDeleteResponder(
|
||||||
await Task.Delay(5.Seconds(), ct);
|
await Task.Delay(5.Seconds(), ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await messageRepository.IsMessageIgnoredAsync(evt.ID.Value, ct))
|
if (await messageRepository.IsMessageIgnoredAsync(evt.ID.Value))
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
var guild = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guild = await guildRepository.GetAsync(evt.GuildID);
|
||||||
if (guild.IsMessageIgnored(evt.ChannelID, evt.ID))
|
if (guild.IsMessageIgnored(evt.ChannelID, evt.ID))
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -34,7 +35,7 @@ public class MessageUpdateResponder(
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
MessageRepository messageRepository,
|
DapperMessageRepository messageRepository,
|
||||||
WebhookExecutorService webhookExecutor,
|
WebhookExecutorService webhookExecutor,
|
||||||
PluralkitApiService pluralkitApi
|
PluralkitApiService pluralkitApi
|
||||||
) : IResponder<IMessageUpdate>
|
) : IResponder<IMessageUpdate>
|
||||||
|
|
@ -58,7 +59,7 @@ public class MessageUpdateResponder(
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(msg.GuildID.Value, false, ct);
|
var guildConfig = await db.GetGuildAsync(msg.GuildID.Value, false, ct);
|
||||||
|
|
||||||
if (await messageRepository.IsMessageIgnoredAsync(msg.ID.Value, ct))
|
if (await messageRepository.IsMessageIgnoredAsync(msg.ID.Value))
|
||||||
{
|
{
|
||||||
_logger.Debug("Message {MessageId} should be ignored", msg.ID);
|
_logger.Debug("Message {MessageId} should be ignored", msg.ID);
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
@ -176,7 +177,7 @@ public class MessageUpdateResponder(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
!await messageRepository.UpdateMessageAsync(msg, ct)
|
!await messageRepository.SaveMessageAsync(msg, ct)
|
||||||
&& msg.ApplicationID.Is(DiscordUtils.PkUserId)
|
&& msg.ApplicationID.Is(DiscordUtils.PkUserId)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -27,7 +28,7 @@ namespace Catalogger.Backend.Bot.Responders.Roles;
|
||||||
|
|
||||||
public class RoleCreateResponder(
|
public class RoleCreateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
) : IResponder<IGuildRoleCreate>
|
) : IResponder<IGuildRoleCreate>
|
||||||
|
|
@ -39,7 +40,7 @@ public class RoleCreateResponder(
|
||||||
_logger.Debug("Received new role {RoleId} in guild {GuildId}", evt.Role.ID, evt.GuildID);
|
_logger.Debug("Received new role {RoleId} in guild {GuildId}", evt.Role.ID, evt.GuildID);
|
||||||
roleCache.Set(evt.Role, evt.GuildID);
|
roleCache.Set(evt.Role, evt.GuildID);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
|
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithTitle("Role created")
|
.WithTitle("Role created")
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
|
|
@ -27,7 +26,7 @@ namespace Catalogger.Backend.Bot.Responders.Roles;
|
||||||
|
|
||||||
public class RoleDeleteResponder(
|
public class RoleDeleteResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
) : IResponder<IGuildRoleDelete>
|
) : IResponder<IGuildRoleDelete>
|
||||||
|
|
@ -47,7 +46,7 @@ public class RoleDeleteResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
|
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithTitle($"Role \"{role.Name}\" deleted")
|
.WithTitle($"Role \"{role.Name}\" deleted")
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
|
@ -29,7 +28,7 @@ namespace Catalogger.Backend.Bot.Responders.Roles;
|
||||||
|
|
||||||
public class RoleUpdateResponder(
|
public class RoleUpdateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
) : IResponder<IGuildRoleUpdate>
|
) : IResponder<IGuildRoleUpdate>
|
||||||
|
|
@ -95,7 +94,7 @@ public class RoleUpdateResponder(
|
||||||
if (embed.Fields.Count == 0)
|
if (embed.Fields.Count == 0)
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
var guildConfig = await guildRepository.GetAsync(evt.GuildID);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildRoleUpdate,
|
LogChannelType.GuildRoleUpdate,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
|
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
|
||||||
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.3"/>
|
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.3"/>
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
||||||
|
|
@ -22,8 +23,10 @@
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
<PackageReference Include="NodaTime" Version="3.1.12"/>
|
<PackageReference Include="NodaTime" Version="3.1.12"/>
|
||||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0"/>
|
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0"/>
|
||||||
|
<PackageReference Include="Npgsql" Version="8.0.5" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8"/>
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="8.0.8"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="8.0.8"/>
|
||||||
|
<PackageReference Include="Npgsql.NodaTime" Version="8.0.5" />
|
||||||
<PackageReference Include="Polly.Core" Version="8.4.2"/>
|
<PackageReference Include="Polly.Core" Version="8.4.2"/>
|
||||||
<PackageReference Include="Polly.RateLimiting" Version="8.4.2"/>
|
<PackageReference Include="Polly.RateLimiting" Version="8.4.2"/>
|
||||||
<PackageReference Include="prometheus-net" Version="8.2.1"/>
|
<PackageReference Include="prometheus-net" Version="8.2.1"/>
|
||||||
|
|
|
||||||
96
Catalogger.Backend/Database/Dapper/DatabaseConnection.cs
Normal file
96
Catalogger.Backend/Database/Dapper/DatabaseConnection.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright (C) 2021-present sam (starshines.gay)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace Catalogger.Backend.Database.Dapper;
|
||||||
|
|
||||||
|
public class DatabaseConnection(Guid id, ILogger logger, NpgsqlConnection inner)
|
||||||
|
: DbConnection,
|
||||||
|
IDisposable
|
||||||
|
{
|
||||||
|
public Guid ConnectionId => id;
|
||||||
|
private readonly ILogger _logger = logger.ForContext<DatabaseConnection>();
|
||||||
|
private readonly DateTimeOffset _openTime = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
private bool _hasClosed;
|
||||||
|
|
||||||
|
public override async Task OpenAsync(CancellationToken cancellationToken) =>
|
||||||
|
await inner.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
public override async Task CloseAsync()
|
||||||
|
{
|
||||||
|
if (_hasClosed)
|
||||||
|
{
|
||||||
|
await inner.CloseAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabasePool.DecrementConnections();
|
||||||
|
var openFor = DateTimeOffset.UtcNow - _openTime;
|
||||||
|
_logger.Debug("Closing connection {ConnId}, open for {OpenFor}", ConnectionId, openFor);
|
||||||
|
_hasClosed = true;
|
||||||
|
await inner.CloseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async ValueTask<DbTransaction> BeginDbTransactionAsync(
|
||||||
|
IsolationLevel isolationLevel,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_logger.Debug("Beginning transaction on connection {ConnId}", ConnectionId);
|
||||||
|
return await inner.BeginTransactionAsync(isolationLevel, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Dispose()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
inner.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await CloseAsync();
|
||||||
|
await inner.DisposeAsync();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) =>
|
||||||
|
inner.BeginTransaction(isolationLevel);
|
||||||
|
|
||||||
|
public override void ChangeDatabase(string databaseName) => inner.ChangeDatabase(databaseName);
|
||||||
|
|
||||||
|
public override void Close() => inner.Close();
|
||||||
|
|
||||||
|
public override void Open() => inner.Open();
|
||||||
|
|
||||||
|
[AllowNull]
|
||||||
|
public override string ConnectionString
|
||||||
|
{
|
||||||
|
get => inner.ConnectionString;
|
||||||
|
set => inner.ConnectionString = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Database => inner.Database;
|
||||||
|
public override ConnectionState State => inner.State;
|
||||||
|
public override string DataSource => inner.DataSource;
|
||||||
|
public override string ServerVersion => inner.ServerVersion;
|
||||||
|
|
||||||
|
protected override DbCommand CreateDbCommand() => inner.CreateCommand();
|
||||||
|
}
|
||||||
157
Catalogger.Backend/Database/Dapper/DatabasePool.cs
Normal file
157
Catalogger.Backend/Database/Dapper/DatabasePool.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright (C) 2021-present sam (starshines.gay)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Data;
|
||||||
|
using Dapper;
|
||||||
|
using NodaTime;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace Catalogger.Backend.Database.Dapper;
|
||||||
|
|
||||||
|
public class DatabasePool
|
||||||
|
{
|
||||||
|
private readonly ILogger _rootLogger;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
|
|
||||||
|
private static int _openConnections;
|
||||||
|
public static int OpenConnections => _openConnections;
|
||||||
|
|
||||||
|
public DatabasePool(Config config, ILogger logger, ILoggerFactory? loggerFactory)
|
||||||
|
{
|
||||||
|
_rootLogger = logger;
|
||||||
|
_logger = logger.ForContext<DatabasePool>();
|
||||||
|
|
||||||
|
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
|
||||||
|
{
|
||||||
|
Timeout = config.Database.Timeout ?? 5,
|
||||||
|
MaxPoolSize = config.Database.MaxPoolSize ?? 50,
|
||||||
|
}.ConnectionString;
|
||||||
|
|
||||||
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
|
||||||
|
dataSourceBuilder.EnableDynamicJson().UseNodaTime();
|
||||||
|
if (config.Logging.LogQueries)
|
||||||
|
dataSourceBuilder.UseLoggerFactory(loggerFactory);
|
||||||
|
_dataSource = dataSourceBuilder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DatabaseConnection> AcquireAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return new DatabaseConnection(
|
||||||
|
LogOpen(),
|
||||||
|
_rootLogger,
|
||||||
|
await _dataSource.OpenConnectionAsync(ct)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseConnection Acquire()
|
||||||
|
{
|
||||||
|
return new DatabaseConnection(LogOpen(), _rootLogger, _dataSource.OpenConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Guid LogOpen()
|
||||||
|
{
|
||||||
|
var connId = Guid.NewGuid();
|
||||||
|
_logger.Debug("Opening database connection {ConnId}", connId);
|
||||||
|
IncrementConnections();
|
||||||
|
return connId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(
|
||||||
|
Func<DatabaseConnection, Task> func,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
await using var conn = await AcquireAsync(ct);
|
||||||
|
await func(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T> ExecuteAsync<T>(
|
||||||
|
Func<DatabaseConnection, Task<T>> func,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
await using var conn = await AcquireAsync(ct);
|
||||||
|
return await func(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IAsyncEnumerable<T>> ExecuteAsync<T>(
|
||||||
|
Func<DatabaseConnection, Task<IAsyncEnumerable<T>>> func,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
await using var conn = await AcquireAsync(ct);
|
||||||
|
return await func(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void IncrementConnections() => Interlocked.Increment(ref _openConnections);
|
||||||
|
|
||||||
|
internal static void DecrementConnections() => Interlocked.Decrement(ref _openConnections);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures Dapper's SQL mapping, as it handles several types incorrectly by default.
|
||||||
|
/// Most notably, ulongs and arrays of ulongs.
|
||||||
|
/// </summary>
|
||||||
|
public static void ConfigureDapper()
|
||||||
|
{
|
||||||
|
DefaultTypeMap.MatchNamesWithUnderscores = true;
|
||||||
|
|
||||||
|
SqlMapper.RemoveTypeMap(typeof(ulong));
|
||||||
|
SqlMapper.AddTypeHandler(new UlongEncodeAsLongHandler());
|
||||||
|
SqlMapper.AddTypeHandler(new UlongArrayHandler());
|
||||||
|
|
||||||
|
SqlMapper.AddTypeHandler(new PassthroughTypeHandler<Instant>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from PluralKit:
|
||||||
|
// https://github.com/PluralKit/PluralKit/blob/4bf60a47d76a068fa0488bf9be96cdaf57a6fe50/PluralKit.Core/Database/Database.cs#L116
|
||||||
|
// Thanks for not working with common types by default, Dapper. Really nice of you.
|
||||||
|
private class PassthroughTypeHandler<T> : SqlMapper.TypeHandler<T>
|
||||||
|
{
|
||||||
|
public override void SetValue(IDbDataParameter parameter, T? value) =>
|
||||||
|
parameter.Value = value;
|
||||||
|
|
||||||
|
public override T Parse(object value) => (T)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UlongEncodeAsLongHandler : SqlMapper.TypeHandler<ulong>
|
||||||
|
{
|
||||||
|
public override ulong Parse(object value) =>
|
||||||
|
// Cast to long to unbox, then to ulong (???)
|
||||||
|
(ulong)(long)value;
|
||||||
|
|
||||||
|
public override void SetValue(IDbDataParameter parameter, ulong value) =>
|
||||||
|
parameter.Value = (long)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UlongArrayHandler : SqlMapper.TypeHandler<ulong[]>
|
||||||
|
{
|
||||||
|
public override void SetValue(IDbDataParameter parameter, ulong[]? value) =>
|
||||||
|
parameter.Value = value != null ? Array.ConvertAll(value, i => (long)i) : null;
|
||||||
|
|
||||||
|
public override ulong[] Parse(object value) =>
|
||||||
|
Array.ConvertAll((long[])value, i => (ulong)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ServiceCollectionDatabaseExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddDatabasePool(this IServiceCollection serviceCollection) =>
|
||||||
|
serviceCollection
|
||||||
|
.AddSingleton<DatabasePool>()
|
||||||
|
.AddScoped<DatabaseConnection>(services =>
|
||||||
|
services.GetRequiredService<DatabasePool>().Acquire()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
// Copyright (C) 2021-present sam (starshines.gay)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using Catalogger.Backend.Extensions;
|
||||||
|
using Dapper;
|
||||||
|
using Remora.Discord.API;
|
||||||
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
|
using Remora.Rest.Core;
|
||||||
|
|
||||||
|
namespace Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
|
|
||||||
|
public class DapperMessageRepository(
|
||||||
|
ILogger logger,
|
||||||
|
DatabaseConnection conn,
|
||||||
|
IEncryptionService encryptionService
|
||||||
|
) : IDisposable, IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = logger.ForContext<DapperMessageRepository>();
|
||||||
|
|
||||||
|
public async Task<Message?> GetMessageAsync(ulong id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
_logger.Debug("Retrieving message {MessageId}", id);
|
||||||
|
|
||||||
|
var dbMsg = await conn.QueryFirstOrDefaultAsync<Models.Message>(
|
||||||
|
"select * from messages where id = @Id",
|
||||||
|
new { Id = id }
|
||||||
|
);
|
||||||
|
if (dbMsg == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Message(
|
||||||
|
dbMsg.Id,
|
||||||
|
dbMsg.OriginalId,
|
||||||
|
dbMsg.UserId,
|
||||||
|
dbMsg.ChannelId,
|
||||||
|
dbMsg.GuildId,
|
||||||
|
dbMsg.Member,
|
||||||
|
dbMsg.System,
|
||||||
|
Username: await Task.Run(() => encryptionService.Decrypt(dbMsg.Username), ct),
|
||||||
|
Content: await Task.Run(() => encryptionService.Decrypt(dbMsg.Content), ct),
|
||||||
|
Metadata: dbMsg.Metadata != null
|
||||||
|
? JsonSerializer.Deserialize<Metadata>(
|
||||||
|
await Task.Run(() => encryptionService.Decrypt(dbMsg.Metadata), ct)
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
dbMsg.AttachmentSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new message. If the message is already in the database, updates the existing message instead.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> SaveMessageAsync(IMessageCreate msg, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var content = await Task.Run(
|
||||||
|
() =>
|
||||||
|
encryptionService.Encrypt(
|
||||||
|
string.IsNullOrWhiteSpace(msg.Content) ? "None" : msg.Content
|
||||||
|
),
|
||||||
|
ct
|
||||||
|
);
|
||||||
|
var username = await Task.Run(() => encryptionService.Encrypt(msg.Author.Tag()), ct);
|
||||||
|
var metadata = await Task.Run(
|
||||||
|
() =>
|
||||||
|
encryptionService.Encrypt(
|
||||||
|
JsonSerializer.Serialize(
|
||||||
|
new Metadata(
|
||||||
|
IsWebhook: msg.WebhookID.HasValue,
|
||||||
|
msg.Attachments.Select(a => new Attachment(
|
||||||
|
a.Filename,
|
||||||
|
a.Size,
|
||||||
|
a.ContentType.Value
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ct
|
||||||
|
);
|
||||||
|
|
||||||
|
// MessageUpdateResponder wants to know whether the message already existed, so query this *before* inserting.
|
||||||
|
var exists = await conn.ExecuteScalarAsync<bool>(
|
||||||
|
"select exists(select id from messages where id = @Id)",
|
||||||
|
new { Id = msg.ID.Value }
|
||||||
|
);
|
||||||
|
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"""
|
||||||
|
insert into messages (id, user_id, channel_id, guild_id, username, content, metadata, attachment_size)
|
||||||
|
values (@Id, @UserId, @ChannelId, @GuildId, @Username, @Content, @Metadata, @AttachmentSize)
|
||||||
|
on conflict (id) do update set username = @Username, content = @Content, metadata = @Metadata
|
||||||
|
""",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = msg.ID.Value,
|
||||||
|
UserId = msg.Author.ID.Value,
|
||||||
|
ChannelId = msg.ChannelID.Value,
|
||||||
|
GuildId = msg.GuildID.Map(s => s.Value).OrDefault(),
|
||||||
|
Content = content,
|
||||||
|
Username = username,
|
||||||
|
Metadata = metadata,
|
||||||
|
AttachmentSize = msg.Attachments.Select(a => a.Size).Sum(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool IsStored, bool HasProxyInfo)> HasProxyInfoAsync(ulong id)
|
||||||
|
{
|
||||||
|
_logger.Debug("Checking if message {MessageId} has proxy information", id);
|
||||||
|
|
||||||
|
var msg = await conn.QueryFirstOrDefaultAsync<(ulong Id, ulong OriginalId)>(
|
||||||
|
"select id, original_id from messages where id = @Id",
|
||||||
|
new { Id = id }
|
||||||
|
);
|
||||||
|
return (msg.Id != 0, msg.OriginalId != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a stored message with PluralKit information.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the message exists and was updated, false if it doesn't exist.</returns>
|
||||||
|
public async Task<bool> SetProxiedMessageDataAsync(
|
||||||
|
ulong id,
|
||||||
|
ulong originalId,
|
||||||
|
ulong authorId,
|
||||||
|
string? systemId,
|
||||||
|
string? memberId
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_logger.Debug("Setting proxy information for message {MessageId}", id);
|
||||||
|
|
||||||
|
var updatedCount = await conn.ExecuteAsync(
|
||||||
|
"update messages set original_id = @OriginalId, user_id = @AuthorId, system = @SystemId, member = @MemberId where id = @Id",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
OriginalId = originalId,
|
||||||
|
AuthorId = authorId,
|
||||||
|
SystemId = systemId,
|
||||||
|
MemberId = memberId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (updatedCount == 0)
|
||||||
|
{
|
||||||
|
_logger.Debug("Message {MessageId} not found, can't set proxy data for it", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsMessageIgnoredAsync(ulong id) =>
|
||||||
|
await conn.ExecuteScalarAsync<bool>(
|
||||||
|
"select exists(select id from messages where id = @Id)",
|
||||||
|
new { Id = id }
|
||||||
|
);
|
||||||
|
|
||||||
|
public const int MaxMessageAgeDays = 15;
|
||||||
|
|
||||||
|
public async Task<(int Messages, int IgnoredMessages)> DeleteExpiredMessagesAsync()
|
||||||
|
{
|
||||||
|
var cutoff = DateTimeOffset.UtcNow - TimeSpan.FromDays(MaxMessageAgeDays);
|
||||||
|
var cutoffId = Snowflake.CreateTimestampSnowflake(cutoff, Constants.DiscordEpoch).Value;
|
||||||
|
|
||||||
|
var msgCount = await conn.ExecuteAsync(
|
||||||
|
"delete from messages where id < @Cutoff",
|
||||||
|
new { Cutoff = cutoffId }
|
||||||
|
);
|
||||||
|
var ignoredMsgCount = await conn.ExecuteAsync(
|
||||||
|
"delete from ignored_messages where id < @Cutoff",
|
||||||
|
new { Cutoff = cutoffId }
|
||||||
|
);
|
||||||
|
|
||||||
|
return (msgCount, ignoredMsgCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task IgnoreMessageAsync(ulong id) =>
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"insert into ignored_messages (id) values (@Id) on conflict do nothing",
|
||||||
|
new { Id = id }
|
||||||
|
);
|
||||||
|
|
||||||
|
public record Message(
|
||||||
|
ulong Id,
|
||||||
|
ulong? OriginalId,
|
||||||
|
ulong UserId,
|
||||||
|
ulong ChannelId,
|
||||||
|
ulong GuildId,
|
||||||
|
string? Member,
|
||||||
|
string? System,
|
||||||
|
string Username,
|
||||||
|
string Content,
|
||||||
|
Metadata? Metadata,
|
||||||
|
int AttachmentSize
|
||||||
|
);
|
||||||
|
|
||||||
|
public record Metadata(bool IsWebhook, IEnumerable<Attachment> Attachments);
|
||||||
|
|
||||||
|
public record Attachment(string Filename, int Size, string ContentType);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await conn.DisposeAsync();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright (C) 2021-present sam (starshines.gay)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using Catalogger.Backend.Database.Models;
|
||||||
|
using Dapper;
|
||||||
|
using Remora.Rest.Core;
|
||||||
|
|
||||||
|
namespace Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
|
|
||||||
|
public class GuildRepository(ILogger logger, DatabaseConnection conn)
|
||||||
|
: IDisposable,
|
||||||
|
IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = logger.ForContext<GuildRepository>();
|
||||||
|
|
||||||
|
public async Task<Guild> GetAsync(Optional<Snowflake> id) => await GetAsync(id.Value.Value);
|
||||||
|
|
||||||
|
public async Task<Guild> GetAsync(Snowflake id) => await GetAsync(id.Value);
|
||||||
|
|
||||||
|
public async Task<Guild> GetAsync(ulong id)
|
||||||
|
{
|
||||||
|
_logger.Debug("Getting guild config for {GuildId}", id);
|
||||||
|
|
||||||
|
var guild = await conn.QueryFirstOrDefaultAsync<Guild>(
|
||||||
|
"select * from guilds where id = @Id",
|
||||||
|
new { Id = id }
|
||||||
|
);
|
||||||
|
if (guild == null)
|
||||||
|
throw new CataloggerError("Guild not found, was not initialized during guild create");
|
||||||
|
return guild;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsGuildKnown(ulong id) =>
|
||||||
|
await conn.ExecuteScalarAsync<bool>(
|
||||||
|
"select exists(select id from guilds where id = @Id)",
|
||||||
|
new { Id = id }
|
||||||
|
);
|
||||||
|
|
||||||
|
public async Task AddGuildAsync(ulong id) =>
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"""
|
||||||
|
insert into guilds (id, key_roles, banned_systems, key_roles, channels)
|
||||||
|
values (@Id, array[]::bigint[], array[]::text[], array[]::bigint[], @Channels)
|
||||||
|
on conflict do nothing
|
||||||
|
""",
|
||||||
|
new { Id = id, Channels = new Guild.ChannelConfig() }
|
||||||
|
);
|
||||||
|
|
||||||
|
public async Task BanSystemAsync(Snowflake guildId, string hid, Guid uuid) =>
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"update guilds set banned_systems = array_cat(banned_systems, @SystemIds) where id = @GuildId",
|
||||||
|
new { GuildId = guildId.Value, SystemIds = (string[])[hid, uuid.ToString()] }
|
||||||
|
);
|
||||||
|
|
||||||
|
public async Task UnbanSystemAsync(Snowflake guildId, string hid, Guid uuid) =>
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"update guilds set banned_systems = array_remove(array_remove(banned_systems, @Hid), @Uuid) where id = @Id",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
GuildId = guildId.Value,
|
||||||
|
Hid = hid,
|
||||||
|
Uuid = uuid.ToString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await conn.DisposeAsync();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,14 +30,11 @@ public class Message
|
||||||
public string? Member { get; set; }
|
public string? Member { get; set; }
|
||||||
public string? System { get; set; }
|
public string? System { get; set; }
|
||||||
|
|
||||||
[Column("username")]
|
public byte[] Username { get; set; } = [];
|
||||||
public byte[] EncryptedUsername { get; set; } = [];
|
|
||||||
|
|
||||||
[Column("content")]
|
public byte[] Content { get; set; } = [];
|
||||||
public byte[] EncryptedContent { get; set; } = [];
|
|
||||||
|
|
||||||
[Column("metadata")]
|
public byte[]? Metadata { get; set; }
|
||||||
public byte[]? EncryptedMetadata { get; set; }
|
|
||||||
|
|
||||||
public int AttachmentSize { get; set; } = 0;
|
public int AttachmentSize { get; set; } = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,18 +47,15 @@ public class MessageRepository(
|
||||||
ChannelId = msg.ChannelID.ToUlong(),
|
ChannelId = msg.ChannelID.ToUlong(),
|
||||||
GuildId = msg.GuildID.ToUlong(),
|
GuildId = msg.GuildID.ToUlong(),
|
||||||
|
|
||||||
EncryptedContent = await Task.Run(
|
Content = await Task.Run(
|
||||||
() =>
|
() =>
|
||||||
encryptionService.Encrypt(
|
encryptionService.Encrypt(
|
||||||
string.IsNullOrWhiteSpace(msg.Content) ? "None" : msg.Content
|
string.IsNullOrWhiteSpace(msg.Content) ? "None" : msg.Content
|
||||||
),
|
),
|
||||||
ct
|
ct
|
||||||
),
|
),
|
||||||
EncryptedUsername = await Task.Run(
|
Username = await Task.Run(() => encryptionService.Encrypt(msg.Author.Tag()), ct),
|
||||||
() => encryptionService.Encrypt(msg.Author.Tag()),
|
Metadata = await Task.Run(
|
||||||
ct
|
|
||||||
),
|
|
||||||
EncryptedMetadata = await Task.Run(
|
|
||||||
() => encryptionService.Encrypt(JsonSerializer.Serialize(metadata)),
|
() => encryptionService.Encrypt(JsonSerializer.Serialize(metadata)),
|
||||||
ct
|
ct
|
||||||
),
|
),
|
||||||
|
|
@ -103,18 +100,15 @@ public class MessageRepository(
|
||||||
"Message was null despite HasProxyInfoAsync returning true"
|
"Message was null despite HasProxyInfoAsync returning true"
|
||||||
);
|
);
|
||||||
|
|
||||||
dbMsg.EncryptedContent = await Task.Run(
|
dbMsg.Content = await Task.Run(
|
||||||
() =>
|
() =>
|
||||||
encryptionService.Encrypt(
|
encryptionService.Encrypt(
|
||||||
string.IsNullOrWhiteSpace(msg.Content) ? "None" : msg.Content
|
string.IsNullOrWhiteSpace(msg.Content) ? "None" : msg.Content
|
||||||
),
|
),
|
||||||
ct
|
ct
|
||||||
);
|
);
|
||||||
dbMsg.EncryptedUsername = await Task.Run(
|
dbMsg.Username = await Task.Run(() => encryptionService.Encrypt(msg.Author.Tag()), ct);
|
||||||
() => encryptionService.Encrypt(msg.Author.Tag()),
|
dbMsg.Metadata = await Task.Run(
|
||||||
ct
|
|
||||||
);
|
|
||||||
dbMsg.EncryptedMetadata = await Task.Run(
|
|
||||||
() => encryptionService.Encrypt(JsonSerializer.Serialize(metadata)),
|
() => encryptionService.Encrypt(JsonSerializer.Serialize(metadata)),
|
||||||
ct
|
ct
|
||||||
);
|
);
|
||||||
|
|
@ -142,11 +136,11 @@ public class MessageRepository(
|
||||||
dbMsg.GuildId,
|
dbMsg.GuildId,
|
||||||
dbMsg.Member,
|
dbMsg.Member,
|
||||||
dbMsg.System,
|
dbMsg.System,
|
||||||
Username: await Task.Run(() => encryptionService.Decrypt(dbMsg.EncryptedUsername), ct),
|
Username: await Task.Run(() => encryptionService.Decrypt(dbMsg.Username), ct),
|
||||||
Content: await Task.Run(() => encryptionService.Decrypt(dbMsg.EncryptedContent), ct),
|
Content: await Task.Run(() => encryptionService.Decrypt(dbMsg.Content), ct),
|
||||||
Metadata: dbMsg.EncryptedMetadata != null
|
Metadata: dbMsg.Metadata != null
|
||||||
? JsonSerializer.Deserialize<Metadata>(
|
? JsonSerializer.Deserialize<Metadata>(
|
||||||
await Task.Run(() => encryptionService.Decrypt(dbMsg.EncryptedMetadata), ct)
|
await Task.Run(() => encryptionService.Decrypt(dbMsg.Metadata), ct)
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
dbMsg.AttachmentSize
|
dbMsg.AttachmentSize
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Cache.RedisCache;
|
using Catalogger.Backend.Cache.RedisCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Database.Redis;
|
using Catalogger.Backend.Database.Redis;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -103,6 +105,9 @@ public static class StartupExtensions
|
||||||
public static IServiceCollection AddCustomServices(this IServiceCollection services) =>
|
public static IServiceCollection AddCustomServices(this IServiceCollection services) =>
|
||||||
services
|
services
|
||||||
.AddSingleton<IClock>(SystemClock.Instance)
|
.AddSingleton<IClock>(SystemClock.Instance)
|
||||||
|
.AddDatabasePool()
|
||||||
|
.AddScoped<DapperMessageRepository>()
|
||||||
|
.AddScoped<GuildRepository>()
|
||||||
.AddSingleton<GuildCache>()
|
.AddSingleton<GuildCache>()
|
||||||
.AddSingleton<RoleCache>()
|
.AddSingleton<RoleCache>()
|
||||||
.AddSingleton<ChannelCache>()
|
.AddSingleton<ChannelCache>()
|
||||||
|
|
@ -113,7 +118,7 @@ public static class StartupExtensions
|
||||||
.AddSingleton<NewsService>()
|
.AddSingleton<NewsService>()
|
||||||
.AddScoped<IEncryptionService, EncryptionService>()
|
.AddScoped<IEncryptionService, EncryptionService>()
|
||||||
.AddSingleton<MetricsCollectionService>()
|
.AddSingleton<MetricsCollectionService>()
|
||||||
.AddScoped<MessageRepository>()
|
// .AddScoped<MessageRepository>()
|
||||||
.AddSingleton<WebhookExecutorService>()
|
.AddSingleton<WebhookExecutorService>()
|
||||||
.AddSingleton<PkMessageHandler>()
|
.AddSingleton<PkMessageHandler>()
|
||||||
.AddSingleton(InMemoryDataService<Snowflake, ChannelCommandData>.Instance)
|
.AddSingleton(InMemoryDataService<Snowflake, ChannelCommandData>.Instance)
|
||||||
|
|
@ -189,6 +194,8 @@ public static class StartupExtensions
|
||||||
.ServiceProvider.GetRequiredService<IClock>()
|
.ServiceProvider.GetRequiredService<IClock>()
|
||||||
.GetCurrentInstant();
|
.GetCurrentInstant();
|
||||||
|
|
||||||
|
DatabasePool.ConfigureDapper();
|
||||||
|
|
||||||
await using (var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>())
|
await using (var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>())
|
||||||
{
|
{
|
||||||
var migrationCount = (await db.Database.GetPendingMigrationsAsync()).Count();
|
var migrationCount = (await db.Database.GetPendingMigrationsAsync()).Count();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
|
|
||||||
namespace Catalogger.Backend.Services;
|
namespace Catalogger.Backend.Services;
|
||||||
|
|
@ -34,7 +34,8 @@ public class BackgroundTasksService(ILogger logger, IServiceProvider services) :
|
||||||
_logger.Information("Running once per minute periodic tasks");
|
_logger.Information("Running once per minute periodic tasks");
|
||||||
|
|
||||||
await using var scope = services.CreateAsyncScope();
|
await using var scope = services.CreateAsyncScope();
|
||||||
var messageRepository = scope.ServiceProvider.GetRequiredService<MessageRepository>();
|
await using var messageRepository =
|
||||||
|
scope.ServiceProvider.GetRequiredService<DapperMessageRepository>();
|
||||||
|
|
||||||
var (msgCount, ignoredCount) = await messageRepository.DeleteExpiredMessagesAsync();
|
var (msgCount, ignoredCount) = await messageRepository.DeleteExpiredMessagesAsync();
|
||||||
if (msgCount != 0 || ignoredCount != 0)
|
if (msgCount != 0 || ignoredCount != 0)
|
||||||
|
|
@ -43,7 +44,7 @@ public class BackgroundTasksService(ILogger logger, IServiceProvider services) :
|
||||||
"Deleted {Count} messages and {IgnoredCount} ignored message IDs older than {MaxDays} days old",
|
"Deleted {Count} messages and {IgnoredCount} ignored message IDs older than {MaxDays} days old",
|
||||||
msgCount,
|
msgCount,
|
||||||
ignoredCount,
|
ignoredCount,
|
||||||
MessageRepository.MaxMessageAgeDays
|
DapperMessageRepository.MaxMessageAgeDays
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
|
"Dapper": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[2.1.35, )",
|
||||||
|
"resolved": "2.1.35",
|
||||||
|
"contentHash": "YKRwjVfrG7GYOovlGyQoMvr1/IJdn+7QzNXJxyMh0YfFF5yvDmTYaJOVYWsckreNjGsGSEtrMTpnzxTUq/tZQw=="
|
||||||
|
},
|
||||||
"EFCore.NamingConventions": {
|
"EFCore.NamingConventions": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[8.0.3, )",
|
"requested": "[8.0.3, )",
|
||||||
|
|
@ -108,6 +114,15 @@
|
||||||
"NodaTime": "[3.0.0, 4.0.0)"
|
"NodaTime": "[3.0.0, 4.0.0)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Npgsql": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[8.0.5, )",
|
||||||
|
"resolved": "8.0.5",
|
||||||
|
"contentHash": "zRG5V8cyeZLpzJlKzFKjEwkRMYIYnHWJvEor2lWXeccS2E1G2nIWYYhnukB51iz5XsWSVEtqg3AxTWM0QJ6vfg==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Npgsql.EntityFrameworkCore.PostgreSQL": {
|
"Npgsql.EntityFrameworkCore.PostgreSQL": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[8.0.8, )",
|
"requested": "[8.0.8, )",
|
||||||
|
|
@ -130,6 +145,16 @@
|
||||||
"Npgsql.NodaTime": "8.0.4"
|
"Npgsql.NodaTime": "8.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Npgsql.NodaTime": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[8.0.5, )",
|
||||||
|
"resolved": "8.0.5",
|
||||||
|
"contentHash": "oC7Ml5TDuQlcGECB5ML0XsPxFrYu3OdpG7c9cuqhB+xunLvqbZ0zXQoPJjvXK9KDNPDB/II61HNdsNas9f2J3A==",
|
||||||
|
"dependencies": {
|
||||||
|
"NodaTime": "3.1.9",
|
||||||
|
"Npgsql": "8.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Polly.Core": {
|
"Polly.Core": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[8.4.2, )",
|
"requested": "[8.4.2, )",
|
||||||
|
|
@ -581,23 +606,6 @@
|
||||||
"resolved": "0.6.7",
|
"resolved": "0.6.7",
|
||||||
"contentHash": "gT6bf5PVayvTuEIuM2XSNqthrtn9W+LlCX4RD//Nb4hrT3agohHvPdjpROgNGgyXDkjwE74F+EwDwqUgJCJG8A=="
|
"contentHash": "gT6bf5PVayvTuEIuM2XSNqthrtn9W+LlCX4RD//Nb4hrT3agohHvPdjpROgNGgyXDkjwE74F+EwDwqUgJCJG8A=="
|
||||||
},
|
},
|
||||||
"Npgsql": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.0.4",
|
|
||||||
"contentHash": "vaYEUlF/pB9m8bs21wQv3Da0kMHT4A9USe47VfY/L2BO97xz5KfIxhEu22QS9d68ZrLxvtL3wQDfDLPr2OjbjA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Npgsql.NodaTime": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.0.4",
|
|
||||||
"contentHash": "nH4yqdl8zC6kCv0kelWhbx0MGBbo7y4rRsAJLEmc2I7NhbvVgBkflYbaC/F1b64UI1TEqJMzcA36MktDSP0Xbw==",
|
|
||||||
"dependencies": {
|
|
||||||
"NodaTime": "3.1.9",
|
|
||||||
"Npgsql": "8.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"OneOf": {
|
"OneOf": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.0.271",
|
"resolved": "3.0.271",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue