diff --git a/Moonlight.ApiServer/Database/CoreDataContext.cs b/Moonlight.ApiServer/Database/CoreDataContext.cs index ab02fc0b..06e6f89f 100644 --- a/Moonlight.ApiServer/Database/CoreDataContext.cs +++ b/Moonlight.ApiServer/Database/CoreDataContext.cs @@ -13,7 +13,15 @@ public class CoreDataContext : DatabaseContext public DbSet Users { get; set; } public DbSet ApiKeys { get; set; } - public CoreDataContext(DatabaseOptions options) : base(options) + public CoreDataContext(AppConfiguration configuration) { + Options = new() + { + Host = configuration.Database.Host, + Port = configuration.Database.Port, + Username = configuration.Database.Username, + Password = configuration.Database.Password, + Database = configuration.Database.Database + }; } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Entities/ApiKey.cs b/Moonlight.ApiServer/Database/Entities/ApiKey.cs index b0e637c2..efe77b7f 100644 --- a/Moonlight.ApiServer/Database/Entities/ApiKey.cs +++ b/Moonlight.ApiServer/Database/Entities/ApiKey.cs @@ -1,4 +1,6 @@ -namespace Moonlight.ApiServer.Database.Entities; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Moonlight.ApiServer.Database.Entities; public class ApiKey { @@ -6,6 +8,10 @@ public class ApiKey public string Secret { get; set; } public string Description { get; set; } + + [Column(TypeName="jsonb")] public string PermissionsJson { get; set; } = "[]"; + + [Column(TypeName = "timestamp with time zone")] public DateTime ExpiresAt { get; set; } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Entities/User.cs b/Moonlight.ApiServer/Database/Entities/User.cs index 91397b0d..13d6b135 100644 --- a/Moonlight.ApiServer/Database/Entities/User.cs +++ b/Moonlight.ApiServer/Database/Entities/User.cs @@ -1,4 +1,6 @@ -namespace Moonlight.ApiServer.Database.Entities; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Moonlight.ApiServer.Database.Entities; public class User { @@ -7,7 +9,10 @@ public class User public string Username { get; set; } public string Email { get; set; } public string Password { get; set; } - + + [Column(TypeName="timestamp with time zone")] public DateTime TokenValidTimestamp { get; set; } = DateTime.MinValue; + + [Column(TypeName="jsonb")] public string PermissionsJson { get; set; } = "[]"; } \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.Designer.cs b/Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.Designer.cs new file mode 100644 index 00000000..8b884463 --- /dev/null +++ b/Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.Designer.cs @@ -0,0 +1,90 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.ApiServer.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Moonlight.ApiServer.Database.Migrations +{ + [DbContext(typeof(CoreDataContext))] + [Migration("20250226080942_AddedUsersAndApiKey")] + partial class AddedUsersAndApiKey + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PermissionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Secret") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Core_ApiKeys", (string)null); + }); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text"); + + b.Property("PermissionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TokenValidTimestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Core_Users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.cs b/Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.cs new file mode 100644 index 00000000..d6024fb0 --- /dev/null +++ b/Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Moonlight.ApiServer.Database.Migrations +{ + /// + public partial class AddedUsersAndApiKey : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Core_ApiKeys", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Secret = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + PermissionsJson = table.Column(type: "jsonb", nullable: false), + ExpiresAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Core_ApiKeys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Core_Users", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Username = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false), + Password = table.Column(type: "text", nullable: false), + TokenValidTimestamp = table.Column(type: "timestamp with time zone", nullable: false), + PermissionsJson = table.Column(type: "jsonb", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Core_Users", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Core_ApiKeys"); + + migrationBuilder.DropTable( + name: "Core_Users"); + } + } +} diff --git a/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs b/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs new file mode 100644 index 00000000..3728cc73 --- /dev/null +++ b/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs @@ -0,0 +1,87 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.ApiServer.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Moonlight.ApiServer.Database.Migrations +{ + [DbContext(typeof(CoreDataContext))] + partial class CoreDataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PermissionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Secret") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Core_ApiKeys", (string)null); + }); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text"); + + b.Property("PermissionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TokenValidTimestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Core_Users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs b/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs index b38011f4..88521220 100644 --- a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs +++ b/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs @@ -1,7 +1,7 @@ +using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi.Models; using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Database; -using Moonlight.ApiServer.Helpers; using Moonlight.ApiServer.Interfaces.Startup; using Moonlight.ApiServer.Services; @@ -39,6 +39,12 @@ public class CoreStartup : IPluginStartup BundleService.BundleCss("css/core.min.css"); + #endregion + + #region Database + + builder.Services.AddDbContext(); + #endregion return Task.CompletedTask; @@ -49,13 +55,6 @@ public class CoreStartup : IPluginStartup return Task.CompletedTask; } - public Task ConfigureDatabase(DatabaseContextCollection collection) - { - collection.Add(); - - return Task.CompletedTask; - } - public Task ConfigureEndpoints(IEndpointRouteBuilder routeBuilder) { if(Configuration.Development.EnableApiDocs) diff --git a/Moonlight.ApiServer/Interfaces/Startup/IPluginStartup.cs b/Moonlight.ApiServer/Interfaces/Startup/IPluginStartup.cs index d97e4741..c0735523 100644 --- a/Moonlight.ApiServer/Interfaces/Startup/IPluginStartup.cs +++ b/Moonlight.ApiServer/Interfaces/Startup/IPluginStartup.cs @@ -1,11 +1,8 @@ -using Moonlight.ApiServer.Helpers; - namespace Moonlight.ApiServer.Interfaces.Startup; public interface IPluginStartup { public Task BuildApplication(IHostApplicationBuilder builder); public Task ConfigureApplication(IApplicationBuilder app); - public Task ConfigureDatabase(DatabaseContextCollection collection); public Task ConfigureEndpoints(IEndpointRouteBuilder routeBuilder); } \ No newline at end of file diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index b88d59e9..5bdfa1dc 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -25,7 +25,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Moonlight.ApiServer/Startup.cs b/Moonlight.ApiServer/Startup.cs index 143ac4e5..b0184c47 100644 --- a/Moonlight.ApiServer/Startup.cs +++ b/Moonlight.ApiServer/Startup.cs @@ -9,14 +9,12 @@ using MoonCore.Extended.Abstractions; using MoonCore.Extended.Extensions; using MoonCore.Extended.Helpers; using MoonCore.Extended.JwtInvalidation; -using MoonCore.Extended.SingleDb; using MoonCore.Extensions; using MoonCore.Helpers; using MoonCore.PluginFramework.Extensions; using MoonCore.Plugins; using MoonCore.Services; using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database; using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Helpers; using Moonlight.ApiServer.Interfaces.Auth; @@ -568,33 +566,17 @@ public class Startup private async Task RegisterDatabase() { - var logger = LoggerFactory.CreateLogger(); - var databaseHelper = new DatabaseHelper(logger); - - var databaseCollection = new DatabaseContextCollection(); - - foreach (var databaseStartup in PluginStartups) - await databaseStartup.ConfigureDatabase(databaseCollection); - - foreach (var database in databaseCollection) - { - databaseHelper.AddDbContext(database); - WebApplicationBuilder.Services.AddScoped(database); - } - - databaseHelper.GenerateMappings(); - - WebApplicationBuilder.Services.AddSingleton(databaseHelper); + WebApplicationBuilder.Services.AddDatabaseMappings(); + WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>)); WebApplicationBuilder.Services.AddScoped(typeof(CrudHelper<,>)); } private async Task PrepareDatabase() { - using var scope = WebApplication.Services.CreateScope(); - var databaseHelper = scope.ServiceProvider.GetRequiredService(); - - await databaseHelper.EnsureMigrated(scope.ServiceProvider); + await WebApplication.Services.EnsureDatabaseMigrated(); + + WebApplication.Services.GenerateDatabaseMappings(); } #endregion @@ -630,7 +612,10 @@ public class Startup var userId = int.Parse(userIdClaim.Value); var userRepository = provider.GetRequiredService>(); - var user = await userRepository.Get().FirstAsync(x => x.Id == userId); + var user = await userRepository.Get().FirstOrDefaultAsync(x => x.Id == userId); + + if(user == null) + return DateTime.MaxValue; return user.TokenValidTimestamp; };