init
This commit is contained in:
commit
ded4f4db26
43 changed files with 2052 additions and 0 deletions
97
Catalogger.Backend/Database/DatabaseContext.cs
Normal file
97
Catalogger.Backend/Database/DatabaseContext.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using Catalogger.Backend.Database.Models;
|
||||
using Catalogger.Backend.Extensions;
|
||||
using EntityFramework.Exceptions.PostgreSQL;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql;
|
||||
|
||||
namespace Catalogger.Backend.Database;
|
||||
|
||||
public class DatabaseContext : DbContext
|
||||
{
|
||||
private readonly NpgsqlDataSource _dataSource;
|
||||
private readonly ILoggerFactory? _loggerFactory;
|
||||
|
||||
public DbSet<Guild> Guilds { get; set; }
|
||||
public DbSet<Message> Messages { get; set; }
|
||||
public DbSet<IgnoredMessage> IgnoredMessages { get; set; }
|
||||
public DbSet<Invite> Invites { get; set; }
|
||||
public DbSet<Watchlist> Watchlists { get; set; }
|
||||
|
||||
public DatabaseContext(Config config, ILoggerFactory? loggerFactory)
|
||||
{
|
||||
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();
|
||||
_dataSource = dataSourceBuilder.Build();
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
=> optionsBuilder
|
||||
.ConfigureWarnings(c =>
|
||||
c.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning)
|
||||
.Ignore(CoreEventId.SaveChangesFailed))
|
||||
.UseNpgsql(_dataSource, o => o.UseNodaTime())
|
||||
.UseSnakeCaseNamingConvention()
|
||||
.UseLoggerFactory(_loggerFactory)
|
||||
.UseExceptionProcessor();
|
||||
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Properties<ulong>().HaveConversion<UlongValueConverter>();
|
||||
configurationBuilder.Properties<List<ulong>>().HaveConversion<UlongArrayValueConverter>();
|
||||
}
|
||||
|
||||
private static readonly ValueComparer<List<ulong>> UlongListValueComparer = new(
|
||||
(c1, c2) => c1 != null && c2 != null && c1.SequenceEqual(c2),
|
||||
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode()))
|
||||
);
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Guild>().Property(g => g.KeyRoles)
|
||||
.Metadata.SetValueComparer(UlongListValueComparer);
|
||||
|
||||
modelBuilder.Entity<Invite>().HasKey(i => i.Code);
|
||||
modelBuilder.Entity<Invite>().HasIndex(i => i.GuildId);
|
||||
|
||||
modelBuilder.Entity<Watchlist>().HasKey(w => new { w.GuildId, w.UserId });
|
||||
modelBuilder.Entity<Watchlist>().Property(w => w.AddedAt).HasDefaultValueSql("now()");
|
||||
}
|
||||
}
|
||||
|
||||
public class DesignTimeDatabaseContextFactory : IDesignTimeDbContextFactory<DatabaseContext>
|
||||
{
|
||||
public DatabaseContext CreateDbContext(string[] args)
|
||||
{
|
||||
// Read the configuration file
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddConfiguration()
|
||||
.Build()
|
||||
// Get the configuration as our config class
|
||||
.Get<Config>() ?? new();
|
||||
|
||||
return new DatabaseContext(config, null);
|
||||
}
|
||||
}
|
||||
|
||||
public class UlongValueConverter() : ValueConverter<ulong, long>(
|
||||
convertToProviderExpression: x => (long)x,
|
||||
convertFromProviderExpression: x => (ulong)x
|
||||
);
|
||||
|
||||
public class UlongArrayValueConverter() : ValueConverter<List<ulong>, List<long>>(
|
||||
convertToProviderExpression: x => x.Select(i => (long)i).ToList(),
|
||||
convertFromProviderExpression: x => x.Select(i => (ulong)i).ToList()
|
||||
);
|
||||
36
Catalogger.Backend/Database/EncryptionService.cs
Normal file
36
Catalogger.Backend/Database/EncryptionService.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Catalogger.Backend.Database;
|
||||
|
||||
public class EncryptionService(Config config) : IEncryptionService
|
||||
{
|
||||
private readonly byte[] _secretKey = Convert.FromBase64String(config.Database.EncryptionKey);
|
||||
|
||||
public byte[] Encrypt(string data)
|
||||
{
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = _secretKey;
|
||||
|
||||
var output = new List<byte>();
|
||||
output.AddRange(aes.IV);
|
||||
|
||||
var plaintext = Encoding.UTF8.GetBytes(data);
|
||||
var ciphertext = aes.EncryptCbc(plaintext, aes.IV);
|
||||
|
||||
output.AddRange(ciphertext);
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
public string Decrypt(byte[] input)
|
||||
{
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = _secretKey;
|
||||
|
||||
var iv = input.Take(aes.IV.Length).ToArray();
|
||||
var ciphertext = input.Skip(aes.IV.Length).ToArray();
|
||||
var plaintext = aes.DecryptCbc(ciphertext, iv);
|
||||
|
||||
return Encoding.UTF8.GetString(plaintext);
|
||||
}
|
||||
}
|
||||
7
Catalogger.Backend/Database/IEncryptionService.cs
Normal file
7
Catalogger.Backend/Database/IEncryptionService.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace Catalogger.Backend.Database;
|
||||
|
||||
public interface IEncryptionService
|
||||
{
|
||||
public byte[] Encrypt(string data);
|
||||
public string Decrypt(byte[] input);
|
||||
}
|
||||
180
Catalogger.Backend/Database/Migrations/20240803132306_Init.Designer.cs
generated
Normal file
180
Catalogger.Backend/Database/Migrations/20240803132306_Init.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
// <auto-generated />
|
||||
using System.Collections.Generic;
|
||||
using Catalogger.Backend.Database;
|
||||
using Catalogger.Backend.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Catalogger.Backend.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20240803132306_Init")]
|
||||
partial class Init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Guild", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<List<string>>("BannedSystems")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]")
|
||||
.HasColumnName("banned_systems");
|
||||
|
||||
b.Property<Guild.ChannelConfig>("Channels")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("channels");
|
||||
|
||||
b.Property<List<long>>("KeyRoles")
|
||||
.IsRequired()
|
||||
.HasColumnType("bigint[]")
|
||||
.HasColumnName("key_roles");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_guilds");
|
||||
|
||||
b.ToTable("guilds", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.IgnoredMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_ignored_messages");
|
||||
|
||||
b.ToTable("ignored_messages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Invite", b =>
|
||||
{
|
||||
b.Property<string>("Code")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("code");
|
||||
|
||||
b.Property<long>("GuildId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Code")
|
||||
.HasName("pk_invites");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
.HasDatabaseName("ix_invites_guild_id");
|
||||
|
||||
b.ToTable("invites", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Message", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AttachmentSize")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("attachment_size");
|
||||
|
||||
b.Property<long>("ChannelId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("channel_id");
|
||||
|
||||
b.Property<byte[]>("EncryptedContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<byte[]>("EncryptedMetadata")
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("metadata");
|
||||
|
||||
b.Property<byte[]>("EncryptedUsername")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("username");
|
||||
|
||||
b.Property<long>("GuildId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<string>("Member")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("member");
|
||||
|
||||
b.Property<long?>("OriginalId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("original_id");
|
||||
|
||||
b.Property<string>("System")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("system");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_messages");
|
||||
|
||||
b.ToTable("messages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Watchlist", b =>
|
||||
{
|
||||
b.Property<long>("GuildId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<Instant>("AddedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("added_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<long>("ModeratorId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("moderator_id");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.HasKey("GuildId", "UserId")
|
||||
.HasName("pk_watchlists");
|
||||
|
||||
b.ToTable("watchlists", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Catalogger.Backend/Database/Migrations/20240803132306_Init.cs
Normal file
115
Catalogger.Backend/Database/Migrations/20240803132306_Init.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using System.Collections.Generic;
|
||||
using Catalogger.Backend.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Catalogger.Backend.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Init : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "guilds",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<long>(type: "bigint", nullable: false),
|
||||
channels = table.Column<Guild.ChannelConfig>(type: "jsonb", nullable: false),
|
||||
banned_systems = table.Column<List<string>>(type: "text[]", nullable: false),
|
||||
key_roles = table.Column<List<long>>(type: "bigint[]", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_guilds", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ignored_messages",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_ignored_messages", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "invites",
|
||||
columns: table => new
|
||||
{
|
||||
code = table.Column<string>(type: "text", nullable: false),
|
||||
guild_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_invites", x => x.code);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "messages",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<long>(type: "bigint", nullable: false),
|
||||
original_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
user_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
channel_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
guild_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
member = table.Column<string>(type: "text", nullable: true),
|
||||
system = table.Column<string>(type: "text", nullable: true),
|
||||
username = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||
content = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||
metadata = table.Column<byte[]>(type: "bytea", nullable: true),
|
||||
attachment_size = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_messages", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "watchlists",
|
||||
columns: table => new
|
||||
{
|
||||
guild_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
user_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
added_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||
moderator_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
reason = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_watchlists", x => new { x.guild_id, x.user_id });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_invites_guild_id",
|
||||
table: "invites",
|
||||
column: "guild_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "guilds");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ignored_messages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "invites");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "messages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "watchlists");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
// <auto-generated />
|
||||
using System.Collections.Generic;
|
||||
using Catalogger.Backend.Database;
|
||||
using Catalogger.Backend.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Catalogger.Backend.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
partial class DatabaseContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Guild", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<List<string>>("BannedSystems")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]")
|
||||
.HasColumnName("banned_systems");
|
||||
|
||||
b.Property<Guild.ChannelConfig>("Channels")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("channels");
|
||||
|
||||
b.Property<List<long>>("KeyRoles")
|
||||
.IsRequired()
|
||||
.HasColumnType("bigint[]")
|
||||
.HasColumnName("key_roles");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_guilds");
|
||||
|
||||
b.ToTable("guilds", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.IgnoredMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_ignored_messages");
|
||||
|
||||
b.ToTable("ignored_messages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Invite", b =>
|
||||
{
|
||||
b.Property<string>("Code")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("code");
|
||||
|
||||
b.Property<long>("GuildId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Code")
|
||||
.HasName("pk_invites");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
.HasDatabaseName("ix_invites_guild_id");
|
||||
|
||||
b.ToTable("invites", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Message", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AttachmentSize")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("attachment_size");
|
||||
|
||||
b.Property<long>("ChannelId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("channel_id");
|
||||
|
||||
b.Property<byte[]>("EncryptedContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<byte[]>("EncryptedMetadata")
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("metadata");
|
||||
|
||||
b.Property<byte[]>("EncryptedUsername")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("username");
|
||||
|
||||
b.Property<long>("GuildId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<string>("Member")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("member");
|
||||
|
||||
b.Property<long?>("OriginalId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("original_id");
|
||||
|
||||
b.Property<string>("System")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("system");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_messages");
|
||||
|
||||
b.ToTable("messages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.Watchlist", b =>
|
||||
{
|
||||
b.Property<long>("GuildId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<Instant>("AddedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("added_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<long>("ModeratorId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("moderator_id");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.HasKey("GuildId", "UserId")
|
||||
.HasName("pk_watchlists");
|
||||
|
||||
b.ToTable("watchlists", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Catalogger.Backend/Database/Models/Guild.cs
Normal file
60
Catalogger.Backend/Database/Models/Guild.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Catalogger.Backend.Extensions;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Database.Models;
|
||||
|
||||
public class Guild
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.None)]
|
||||
public required ulong Id { get; init; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public ChannelConfig Channels { get; init; } = new();
|
||||
public List<string> BannedSystems { get; init; } = [];
|
||||
public List<ulong> KeyRoles { get; init; } = [];
|
||||
|
||||
public bool IsMessageIgnored(Snowflake channelId, Snowflake userId)
|
||||
{
|
||||
if (Channels is { MessageDelete: 0, MessageUpdate: 0, MessageDeleteBulk: 0 } ||
|
||||
Channels.IgnoredChannels.Contains(channelId.ToUlong()) ||
|
||||
Channels.IgnoredUsers.Contains(userId.ToUlong())) return true;
|
||||
|
||||
if (Channels.IgnoredUsersPerChannel.TryGetValue(channelId.ToUlong(),
|
||||
out var thisChannelIgnoredUsers))
|
||||
return thisChannelIgnoredUsers.Contains(userId.ToUlong());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public class ChannelConfig
|
||||
{
|
||||
public List<ulong> IgnoredChannels { get; init; } = [];
|
||||
public List<ulong> IgnoredUsers { get; init; } = [];
|
||||
public Dictionary<ulong, List<ulong>> IgnoredUsersPerChannel { get; init; } = [];
|
||||
public Dictionary<ulong, ulong> Redirects { get; init; } = [];
|
||||
|
||||
public ulong GuildUpdate { get; init; }
|
||||
public ulong GuildEmojisUpdate { get; init; }
|
||||
public ulong GuildRoleCreate { get; init; }
|
||||
public ulong GuildRoleUpdate { get; init; }
|
||||
public ulong GuildRoleDelete { get; init; }
|
||||
public ulong ChannelCreate { get; init; }
|
||||
public ulong ChannelUpdate { get; init; }
|
||||
public ulong ChannelDelete { get; init; }
|
||||
public ulong GuildMemberAdd { get; init; }
|
||||
public ulong GuildMemberUpdate { get; init; }
|
||||
public ulong GuildKeyRoleUpdate { get; init; }
|
||||
public ulong GuildMemberNickUpdate { get; init; }
|
||||
public ulong GuildMemberAvatarUpdate { get; init; }
|
||||
public ulong GuildMemberRemove { get; init; }
|
||||
public ulong GuildMemberKick { get; init; }
|
||||
public ulong GuildBanAdd { get; init; }
|
||||
public ulong GuildBanRemove { get; init; }
|
||||
public ulong InviteCreate { get; init; }
|
||||
public ulong InviteDelete { get; init; }
|
||||
public ulong MessageUpdate { get; init; }
|
||||
public ulong MessageDelete { get; init; }
|
||||
public ulong MessageDeleteBulk { get; init; }
|
||||
}
|
||||
}
|
||||
8
Catalogger.Backend/Database/Models/Invite.cs
Normal file
8
Catalogger.Backend/Database/Models/Invite.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace Catalogger.Backend.Database.Models;
|
||||
|
||||
public class Invite
|
||||
{
|
||||
public required ulong GuildId { get; init; }
|
||||
public required string Code { get; init; }
|
||||
public required string Name { get; set; }
|
||||
}
|
||||
27
Catalogger.Backend/Database/Models/Message.cs
Normal file
27
Catalogger.Backend/Database/Models/Message.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Catalogger.Backend.Database.Models;
|
||||
|
||||
public class Message
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.None)]
|
||||
public required ulong Id { get; init; }
|
||||
|
||||
public ulong? OriginalId { get; set; }
|
||||
public required ulong UserId { get; set; }
|
||||
public required ulong ChannelId { get; init; }
|
||||
public required ulong GuildId { get; init; }
|
||||
|
||||
public string? Member { get; set; }
|
||||
public string? System { get; set; }
|
||||
|
||||
[Column("username")] public byte[] EncryptedUsername { get; set; } = [];
|
||||
[Column("content")] public byte[] EncryptedContent { get; set; } = [];
|
||||
[Column("metadata")] public byte[]? EncryptedMetadata { get; set; }
|
||||
|
||||
public int AttachmentSize { get; set; } = 0;
|
||||
}
|
||||
|
||||
public record IgnoredMessage(
|
||||
[property: DatabaseGenerated(DatabaseGeneratedOption.None)]
|
||||
ulong Id);
|
||||
13
Catalogger.Backend/Database/Models/Watchlist.cs
Normal file
13
Catalogger.Backend/Database/Models/Watchlist.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using NodaTime;
|
||||
|
||||
namespace Catalogger.Backend.Database.Models;
|
||||
|
||||
public class Watchlist
|
||||
{
|
||||
public required ulong GuildId { get; init; }
|
||||
public required ulong UserId { get; init; }
|
||||
public Instant AddedAt { get; init; }
|
||||
|
||||
public required ulong ModeratorId { get; set; }
|
||||
public required string Reason { get; set; }
|
||||
}
|
||||
99
Catalogger.Backend/Database/Queries/MessageRepository.cs
Normal file
99
Catalogger.Backend/Database/Queries/MessageRepository.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
using Catalogger.Backend.Database.Models;
|
||||
using Catalogger.Backend.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||
using DbMessage = Catalogger.Backend.Database.Models.Message;
|
||||
|
||||
namespace Catalogger.Backend.Database.Queries;
|
||||
|
||||
public class MessageRepository(ILogger logger, DatabaseContext db, IEncryptionService encryptionService)
|
||||
{
|
||||
private readonly ILogger _logger = logger.ForContext<MessageRepository>();
|
||||
|
||||
public async Task SaveMessageAsync(IMessageCreate msg, CancellationToken ct = default)
|
||||
{
|
||||
_logger.Debug("Saving message {MessageId}", msg.ID);
|
||||
|
||||
var dbMessage = new DbMessage
|
||||
{
|
||||
Id = msg.ID.ToUlong(),
|
||||
UserId = msg.Author.ID.ToUlong(),
|
||||
ChannelId = msg.ChannelID.ToUlong(),
|
||||
GuildId = msg.GuildID.ToUlong(),
|
||||
|
||||
EncryptedContent = await Task.Run(() => encryptionService.Encrypt(msg.Content), ct),
|
||||
EncryptedUsername = await Task.Run(() => encryptionService.Encrypt(msg.Author.Tag()), ct),
|
||||
AttachmentSize = msg.Attachments.Select(a => a.Size).Sum()
|
||||
};
|
||||
|
||||
db.Add(dbMessage);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<Message?> GetMessageAsync(ulong id, CancellationToken ct = default)
|
||||
{
|
||||
_logger.Debug("Retrieving message {MessageId}", id);
|
||||
|
||||
var dbMsg = await db.Messages.FindAsync(id);
|
||||
if (dbMsg == null) return null;
|
||||
|
||||
return new Message(dbMsg.Id, dbMsg.OriginalId, dbMsg.UserId, dbMsg.ChannelId, dbMsg.GuildId, dbMsg.Member,
|
||||
dbMsg.System, await Task.Run(() => encryptionService.Decrypt(dbMsg.EncryptedUsername), ct),
|
||||
await Task.Run(() => encryptionService.Decrypt(dbMsg.EncryptedContent), ct), null, dbMsg.AttachmentSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a message has proxy information.
|
||||
/// If yes, returns (true, true). If no, returns (true, false). If the message isn't saved at all, returns (false, false).
|
||||
/// </summary>
|
||||
public async Task<(bool, bool)> HasProxyInfoAsync(ulong id)
|
||||
{
|
||||
_logger.Debug("Checking if message {MessageId} has proxy information", id);
|
||||
|
||||
var msg = await db.Messages.Select(m => new { m.Id, m.OriginalId }).FirstOrDefaultAsync(m => m.Id == id);
|
||||
return (msg != null, msg?.OriginalId != null);
|
||||
}
|
||||
|
||||
public async Task SetProxiedMessageDataAsync(ulong id, ulong originalId, ulong authorId, string? systemId,
|
||||
string? memberId)
|
||||
{
|
||||
_logger.Debug("Setting proxy information for message {MessageId}", id);
|
||||
|
||||
var message = await db.Messages.FirstOrDefaultAsync(m => m.Id == id);
|
||||
if (message == null)
|
||||
{
|
||||
_logger.Debug("Message {MessageId} not found", id);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug("Updating message {MessageId}", id);
|
||||
|
||||
message.OriginalId = originalId;
|
||||
message.UserId = authorId;
|
||||
message.System = systemId;
|
||||
message.Member = memberId;
|
||||
|
||||
db.Update(message);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> IsMessageIgnoredAsync(ulong id, CancellationToken ct = default)
|
||||
{
|
||||
_logger.Debug("Checking if message {MessageId} is ignored", id);
|
||||
return await db.IgnoredMessages.FirstOrDefaultAsync(m => m.Id == id, ct) != null;
|
||||
}
|
||||
|
||||
public record Message(
|
||||
ulong Id,
|
||||
ulong? OriginalId,
|
||||
ulong UserId,
|
||||
ulong ChannelId,
|
||||
ulong GuildId,
|
||||
string? Member,
|
||||
string? System,
|
||||
string Username,
|
||||
string Content,
|
||||
string? Metadata,
|
||||
int AttachmentSize
|
||||
);
|
||||
}
|
||||
22
Catalogger.Backend/Database/Queries/QueryExtensions.cs
Normal file
22
Catalogger.Backend/Database/Queries/QueryExtensions.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using Catalogger.Backend.Database.Models;
|
||||
using Catalogger.Backend.Extensions;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Database.Queries;
|
||||
|
||||
public static class QueryExtensions
|
||||
{
|
||||
public static async ValueTask<Guild> GetGuildAsync(this DatabaseContext db, Snowflake id,
|
||||
CancellationToken ct = default) => await db.GetGuildAsync(id.ToUlong(), ct);
|
||||
|
||||
public static async ValueTask<Guild> GetGuildAsync(this DatabaseContext db, Optional<Snowflake> id,
|
||||
CancellationToken ct = default) => await db.GetGuildAsync(id.ToUlong(), ct);
|
||||
|
||||
public static async ValueTask<Guild> GetGuildAsync(this DatabaseContext db, ulong id,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var guild = await db.Guilds.FindAsync(id);
|
||||
if (guild == null) throw new Exception("oh");
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
3
Catalogger.Backend/Database/QueryUtils.cs
Normal file
3
Catalogger.Backend/Database/QueryUtils.cs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
using NodaTime;
|
||||
|
||||
namespace Catalogger.Backend.Database;
|
||||
Loading…
Add table
Add a link
Reference in a new issue