excise entity framework from all remaining code
This commit is contained in:
parent
d6c3133d52
commit
ff92c5f335
62 changed files with 402 additions and 1339 deletions
|
|
@ -18,7 +18,7 @@ using System.Data.Common;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Npgsql;
|
||||
|
||||
namespace Catalogger.Backend.Database.Dapper;
|
||||
namespace Catalogger.Backend.Database;
|
||||
|
||||
public class DatabaseConnection(Guid id, ILogger logger, NpgsqlConnection inner)
|
||||
: DbConnection,
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
// 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 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 DbSet<ApiToken> ApiTokens { 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()))
|
||||
);
|
||||
|
||||
private static readonly ValueComparer<ulong[]> UlongArrayValueComparer =
|
||||
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(UlongArrayValueComparer);
|
||||
|
||||
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()
|
||||
);
|
||||
145
Catalogger.Backend/Database/DatabaseMigrator.cs
Normal file
145
Catalogger.Backend/Database/DatabaseMigrator.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// 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.Common;
|
||||
using Dapper;
|
||||
using NodaTime;
|
||||
|
||||
namespace Catalogger.Backend.Database;
|
||||
|
||||
public class DatabaseMigrator(ILogger logger, IClock clock, DatabaseConnection conn)
|
||||
: IDisposable,
|
||||
IAsyncDisposable
|
||||
{
|
||||
private const string RootPath = "Catalogger.Backend.Database";
|
||||
private static readonly int MigrationsPathLength = $"{RootPath}.Migrations.".Length;
|
||||
|
||||
public async Task Migrate()
|
||||
{
|
||||
var migrations = GetMigrationNames().ToArray();
|
||||
logger.Debug("Getting current database migration");
|
||||
var currentMigration = await GetCurrentMigration();
|
||||
if (currentMigration != null)
|
||||
migrations = migrations
|
||||
.Where(s => string.CompareOrdinal(s, currentMigration.MigrationName) > 0)
|
||||
.ToArray();
|
||||
|
||||
logger.Information(
|
||||
"Current migration: {Migration}. Applying {Count} migrations",
|
||||
currentMigration?.MigrationName,
|
||||
migrations.Length
|
||||
);
|
||||
if (migrations.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap all migrations in a transaction
|
||||
await using var tx = await conn.BeginTransactionAsync();
|
||||
var totalStartTime = clock.GetCurrentInstant();
|
||||
foreach (var migration in migrations)
|
||||
{
|
||||
logger.Debug("Executing migration {Migration}", migration);
|
||||
var startTime = clock.GetCurrentInstant();
|
||||
await ExecuteMigration(tx, migration);
|
||||
var took = clock.GetCurrentInstant() - startTime;
|
||||
logger.Debug("Executed migration {Migration} in {Took}", migration, took);
|
||||
}
|
||||
|
||||
var totalTook = clock.GetCurrentInstant() - totalStartTime;
|
||||
logger.Information("Executed {Count} migrations in {Took}", migrations.Length, totalTook);
|
||||
|
||||
// Finally, commit the transaction
|
||||
await tx.CommitAsync();
|
||||
}
|
||||
|
||||
private async Task ExecuteMigration(DbTransaction tx, string migrationName)
|
||||
{
|
||||
var query = await GetResource($"{RootPath}.Migrations.{migrationName}.up.sql");
|
||||
|
||||
// Run the migration
|
||||
await conn.ExecuteAsync(query, transaction: tx);
|
||||
// Store that we ran the migration
|
||||
await conn.ExecuteAsync(
|
||||
"INSERT INTO migrations (migration_name, applied_at) VALUES (@MigrationName, @AppliedAt)",
|
||||
new { MigrationName = migrationName, AppliedAt = clock.GetCurrentInstant() }
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the current migration. If no migrations have been applied, returns null
|
||||
private async Task<MigrationEntry?> GetCurrentMigration()
|
||||
{
|
||||
// Check if the migrations table exists
|
||||
var hasMigrationTable =
|
||||
await conn.QuerySingleOrDefaultAsync<int>(
|
||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'migrations'"
|
||||
) == 1;
|
||||
// If so, return the current migration
|
||||
if (hasMigrationTable)
|
||||
{
|
||||
return await conn.QuerySingleOrDefaultAsync<MigrationEntry>(
|
||||
"SELECT * FROM migrations ORDER BY applied_at DESC LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
logger.Debug("Migrations table does not exist, assuming this is a new database");
|
||||
|
||||
// Else, create the migrations table then return null
|
||||
var migrationTableQuery = await GetResource($"{RootPath}.setup_migrations.sql");
|
||||
await conn.ExecuteAsync(migrationTableQuery);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns a resource by name as a string.
|
||||
private static async Task<string> GetResource(string name)
|
||||
{
|
||||
await using var stream =
|
||||
typeof(DatabasePool).Assembly.GetManifestResourceStream(name)
|
||||
?? throw new ArgumentException($"Invalid resource '{name}'");
|
||||
using var reader = new StreamReader(stream);
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetMigrationNames() =>
|
||||
typeof(DatabasePool)
|
||||
.Assembly.GetManifestResourceNames()
|
||||
.Where(s => s.StartsWith($"{RootPath}.Migrations"))
|
||||
.Where(s => s.EndsWith(".up.sql"))
|
||||
.Select(s =>
|
||||
s.Substring(
|
||||
MigrationsPathLength,
|
||||
s.Length - MigrationsPathLength - ".up.sql".Length
|
||||
)
|
||||
)
|
||||
.OrderBy(s => s);
|
||||
|
||||
private record MigrationEntry
|
||||
{
|
||||
public string MigrationName { get; init; } = null!;
|
||||
public Instant AppliedAt { get; init; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
conn.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await conn.DisposeAsync();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,13 +15,12 @@
|
|||
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Catalogger.Backend.Database.Models;
|
||||
using Dapper;
|
||||
using NodaTime;
|
||||
using Npgsql;
|
||||
|
||||
namespace Catalogger.Backend.Database.Dapper;
|
||||
namespace Catalogger.Backend.Database;
|
||||
|
||||
public class DatabasePool
|
||||
{
|
||||
6
Catalogger.Backend/Database/Migrations/001_init.down.sql
Normal file
6
Catalogger.Backend/Database/Migrations/001_init.down.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
drop table api_tokens;
|
||||
drop table watchlists;
|
||||
drop table messages;
|
||||
drop table invites;
|
||||
drop table ignored_messages;
|
||||
drop table guilds;
|
||||
60
Catalogger.Backend/Database/Migrations/001_init.up.sql
Normal file
60
Catalogger.Backend/Database/Migrations/001_init.up.sql
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
create table if not exists guilds
|
||||
(
|
||||
id bigint not null primary key,
|
||||
channels jsonb not null,
|
||||
banned_systems text[] not null,
|
||||
key_roles bigint[] not null
|
||||
);
|
||||
|
||||
create table if not exists ignored_messages
|
||||
(
|
||||
id bigint not null primary key
|
||||
);
|
||||
|
||||
create table if not exists invites
|
||||
(
|
||||
code text not null primary key,
|
||||
guild_id bigint not null,
|
||||
name text not null
|
||||
);
|
||||
|
||||
create index if not exists ix_invites_guild_id on invites (guild_id);
|
||||
|
||||
create table if not exists messages
|
||||
(
|
||||
id bigint not null primary key,
|
||||
original_id bigint,
|
||||
user_id bigint not null,
|
||||
channel_id bigint not null,
|
||||
guild_id bigint not null,
|
||||
member text,
|
||||
system text,
|
||||
username bytea not null,
|
||||
content bytea not null,
|
||||
metadata bytea,
|
||||
attachment_size integer not null
|
||||
);
|
||||
|
||||
create table if not exists watchlists
|
||||
(
|
||||
guild_id bigint not null,
|
||||
user_id bigint not null,
|
||||
added_at timestamp with time zone default now() not null,
|
||||
moderator_id bigint not null,
|
||||
reason text not null,
|
||||
|
||||
primary key (guild_id, user_id)
|
||||
);
|
||||
|
||||
create table if not exists api_tokens
|
||||
(
|
||||
id integer generated by default as identity primary key,
|
||||
dashboard_token text not null,
|
||||
user_id text not null,
|
||||
access_token text not null,
|
||||
refresh_token text,
|
||||
expires_at timestamp with time zone not null
|
||||
);
|
||||
|
||||
-- Finally, drop the EFCore migrations table.
|
||||
drop table if exists "__EFMigrationsHistory";
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
// <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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
// <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("20241017130936_AddDashboardTokens")]
|
||||
partial class AddDashboardTokens
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.8")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.ApiToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AccessToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("access_token");
|
||||
|
||||
b.Property<string>("DashboardToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("dashboard_token");
|
||||
|
||||
b.Property<Instant>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expires_at");
|
||||
|
||||
b.Property<string>("RefreshToken")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("refresh_token");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_api_tokens");
|
||||
|
||||
b.ToTable("api_tokens", (string)null);
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Catalogger.Backend.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDashboardTokens : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "api_tokens",
|
||||
columns: table => new
|
||||
{
|
||||
id = table
|
||||
.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation(
|
||||
"Npgsql:ValueGenerationStrategy",
|
||||
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn
|
||||
),
|
||||
dashboard_token = table.Column<string>(type: "text", nullable: false),
|
||||
user_id = table.Column<string>(type: "text", nullable: false),
|
||||
access_token = table.Column<string>(type: "text", nullable: false),
|
||||
refresh_token = table.Column<string>(type: "text", nullable: true),
|
||||
expires_at = table.Column<Instant>(
|
||||
type: "timestamp with time zone",
|
||||
nullable: false
|
||||
),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_api_tokens", x => x.id);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(name: "api_tokens");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
// <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.8")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Catalogger.Backend.Database.Models.ApiToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AccessToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("access_token");
|
||||
|
||||
b.Property<string>("DashboardToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("dashboard_token");
|
||||
|
||||
b.Property<Instant>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expires_at");
|
||||
|
||||
b.Property<string>("RefreshToken")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("refresh_token");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_api_tokens");
|
||||
|
||||
b.ToTable("api_tokens", (string)null);
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// 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 Catalogger.Backend.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Database.Queries;
|
||||
|
||||
public static class QueryExtensions
|
||||
{
|
||||
public static async ValueTask<Guild> GetGuildAsync(
|
||||
this DatabaseContext db,
|
||||
Snowflake id,
|
||||
bool shouldTrack = true,
|
||||
CancellationToken ct = default
|
||||
) => await db.GetGuildAsync(id.ToUlong(), shouldTrack, ct);
|
||||
|
||||
public static async ValueTask<Guild> GetGuildAsync(
|
||||
this DatabaseContext db,
|
||||
Optional<Snowflake> id,
|
||||
bool shouldTrack = true,
|
||||
CancellationToken ct = default
|
||||
) => await db.GetGuildAsync(id.ToUlong(), shouldTrack, ct);
|
||||
|
||||
public static async ValueTask<Guild> GetGuildAsync(
|
||||
this DatabaseContext db,
|
||||
ulong id,
|
||||
bool shouldTrack = true,
|
||||
CancellationToken ct = default
|
||||
)
|
||||
{
|
||||
Guild? guild;
|
||||
if (shouldTrack)
|
||||
guild = await db.Guilds.FindAsync([id], ct);
|
||||
else
|
||||
guild = await db.Guilds.AsNoTracking().FirstOrDefaultAsync(g => g.Id == id, ct);
|
||||
|
||||
if (guild == null)
|
||||
throw new CataloggerError("Guild not found, was not initialized during guild create");
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// 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 NodaTime;
|
||||
|
||||
namespace Catalogger.Backend.Database.Repositories;
|
||||
|
||||
public class ApiTokenRepository(ILogger logger, DatabaseConnection conn, IClock clock)
|
||||
: IDisposable,
|
||||
IAsyncDisposable
|
||||
{
|
||||
private readonly ILogger _logger = logger.ForContext<ApiTokenRepository>();
|
||||
|
||||
public async Task<ApiToken?> GetAsync(string token) =>
|
||||
await conn.QueryFirstOrDefaultAsync<ApiToken>(
|
||||
"select * from api_tokens where dashboard_token = @Token and expires_at > @Now",
|
||||
new { Token = token, Now = clock.GetCurrentInstant() }
|
||||
);
|
||||
|
||||
public async Task<ApiToken> CreateAsync(
|
||||
string dashboardToken,
|
||||
string userId,
|
||||
string accessToken,
|
||||
string? refreshToken,
|
||||
int expiresIn
|
||||
)
|
||||
{
|
||||
var expiresAt = clock.GetCurrentInstant() + Duration.FromSeconds(expiresIn);
|
||||
|
||||
return await conn.QueryFirstAsync<ApiToken>(
|
||||
"""
|
||||
insert into api_tokens (dashboard_token, user_id, access_token, refresh_token, expires_at)
|
||||
values (@dashboardToken, @userId, @accessToken, @refreshToken, @expiresAt)
|
||||
returning *
|
||||
""",
|
||||
new
|
||||
{
|
||||
dashboardToken,
|
||||
userId,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresAt,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<ApiToken> UpdateAsync(
|
||||
int id,
|
||||
string accessToken,
|
||||
string? refreshToken,
|
||||
int expiresIn
|
||||
)
|
||||
{
|
||||
var expiresAt = clock.GetCurrentInstant() + Duration.FromSeconds(expiresIn);
|
||||
|
||||
return await conn.QueryFirstAsync<ApiToken>(
|
||||
"""
|
||||
update api_tokens set access_token = @accessToken, refresh_token = @refreshToken,
|
||||
expires_at = @expiresAt where id = @id
|
||||
returning *
|
||||
""",
|
||||
new
|
||||
{
|
||||
id,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresAt,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
conn.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await conn.DisposeAsync();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ using Catalogger.Backend.Database.Models;
|
|||
using Dapper;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Database.Dapper.Repositories;
|
||||
namespace Catalogger.Backend.Database.Repositories;
|
||||
|
||||
public class GuildRepository(ILogger logger, DatabaseConnection conn)
|
||||
: IDisposable,
|
||||
|
|
@ -89,8 +89,8 @@ public class GuildRepository(ILogger logger, DatabaseConnection conn)
|
|||
|
||||
public async Task UpdateChannelConfigAsync(Snowflake id, Guild.ChannelConfig config) =>
|
||||
await conn.ExecuteAsync(
|
||||
"update guilds set channels = @Channels where id = @Id",
|
||||
new { Id = id, Channels = config }
|
||||
"update guilds set channels = @Channels::jsonb where id = @Id",
|
||||
new { Id = id.Value, Channels = config }
|
||||
);
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -17,7 +17,7 @@ using Catalogger.Backend.Database.Models;
|
|||
using Dapper;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Database.Dapper.Repositories;
|
||||
namespace Catalogger.Backend.Database.Repositories;
|
||||
|
||||
public class InviteRepository(ILogger logger, DatabaseConnection conn)
|
||||
: IDisposable,
|
||||
|
|
@ -20,7 +20,7 @@ using Remora.Discord.API;
|
|||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Database.Dapper.Repositories;
|
||||
namespace Catalogger.Backend.Database.Repositories;
|
||||
|
||||
public class MessageRepository(
|
||||
ILogger logger,
|
||||
|
|
@ -17,7 +17,7 @@ using Catalogger.Backend.Database.Models;
|
|||
using Dapper;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Database.Dapper.Repositories;
|
||||
namespace Catalogger.Backend.Database.Repositories;
|
||||
|
||||
public class WatchlistRepository(ILogger logger, DatabaseConnection conn)
|
||||
: IDisposable,
|
||||
4
Catalogger.Backend/Database/setup_migrations.sql
Normal file
4
Catalogger.Backend/Database/setup_migrations.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
create table if not exists migrations (
|
||||
migration_name text primary key,
|
||||
applied_at timestamptz not null default (now())
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue