Expanding theme tab to customization tab. Started improving theme selection.
This commit is contained in:
@@ -3,16 +3,18 @@ using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.SingleDb;
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Models;
|
||||
|
||||
namespace Moonlight.ApiServer.Database;
|
||||
|
||||
public class CoreDataContext : DatabaseContext
|
||||
{
|
||||
public override string Prefix { get; } = "Core";
|
||||
|
||||
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<ApiKey> ApiKeys { get; set; }
|
||||
|
||||
public DbSet<Theme> Themes { get; set; }
|
||||
|
||||
public CoreDataContext(AppConfiguration configuration)
|
||||
{
|
||||
Options = new()
|
||||
@@ -29,5 +31,12 @@ public class CoreDataContext : DatabaseContext
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.OnHangfireModelCreating();
|
||||
|
||||
modelBuilder.Ignore<ApplicationTheme>();
|
||||
modelBuilder.Entity<Theme>()
|
||||
.OwnsOne(x => x.Content, builder =>
|
||||
{
|
||||
builder.ToJson();
|
||||
});
|
||||
}
|
||||
}
|
||||
19
Moonlight.ApiServer/Database/Entities/Theme.cs
Normal file
19
Moonlight.ApiServer/Database/Entities/Theme.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Moonlight.ApiServer.Models;
|
||||
|
||||
namespace Moonlight.ApiServer.Database.Entities;
|
||||
|
||||
public class Theme
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Version { get; set; }
|
||||
|
||||
public string? UpdateUrl { get; set; }
|
||||
public string? DonateUrl { get; set; }
|
||||
|
||||
public ApplicationTheme Content { get; set; }
|
||||
}
|
||||
564
Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs
generated
Normal file
564
Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs
generated
Normal file
@@ -0,0 +1,564 @@
|
||||
// <auto-generated />
|
||||
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("20250720203346_AddedThemes")]
|
||||
partial class AddedThemes
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime?>("ExpireAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<long>("Value")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExpireAt");
|
||||
|
||||
b.HasIndex("Key", "Value");
|
||||
|
||||
b.ToTable("HangfireCounter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Field")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<DateTime?>("ExpireAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Key", "Field");
|
||||
|
||||
b.HasIndex("ExpireAt");
|
||||
|
||||
b.ToTable("HangfireHash");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime?>("ExpireAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("InvocationData")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long?>("StateId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("StateName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExpireAt");
|
||||
|
||||
b.HasIndex("StateId");
|
||||
|
||||
b.HasIndex("StateName");
|
||||
|
||||
b.ToTable("HangfireJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||
{
|
||||
b.Property<long>("JobId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("JobId", "Name");
|
||||
|
||||
b.ToTable("HangfireJobParameter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime?>("ExpireAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Key", "Position");
|
||||
|
||||
b.HasIndex("ExpireAt");
|
||||
|
||||
b.ToTable("HangfireList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<DateTime>("AcquiredAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("HangfireLock");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime?>("FetchedAt")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long>("JobId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Queue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("JobId");
|
||||
|
||||
b.HasIndex("Queue", "FetchedAt");
|
||||
|
||||
b.ToTable("HangfireQueuedJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<DateTime>("Heartbeat")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Queues")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("StartedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("WorkerCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Heartbeat");
|
||||
|
||||
b.ToTable("HangfireServer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<DateTime?>("ExpireAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<double>("Score")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.HasKey("Key", "Value");
|
||||
|
||||
b.HasIndex("ExpireAt");
|
||||
|
||||
b.HasIndex("Key", "Score");
|
||||
|
||||
b.ToTable("HangfireSet");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("JobId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("JobId");
|
||||
|
||||
b.ToTable("HangfireState");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Core_ApiKeys", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Core_Themes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Core_Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||
{
|
||||
b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State")
|
||||
.WithMany()
|
||||
.HasForeignKey("StateId");
|
||||
|
||||
b.Navigation("State");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||
{
|
||||
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||
.WithMany("Parameters")
|
||||
.HasForeignKey("JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Job");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||
{
|
||||
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||
.WithMany("QueuedJobs")
|
||||
.HasForeignKey("JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Job");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||
{
|
||||
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||
.WithMany("States")
|
||||
.HasForeignKey("JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Job");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||
{
|
||||
b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 =>
|
||||
{
|
||||
b1.Property<int>("ThemeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<float>("Border")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<string>("ColorAccent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorAccentContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBackground")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase100")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase150")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase200")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase250")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase300")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBaseContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorError")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorErrorContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorInfo")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorInfoContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorNeutral")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorNeutralContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorPrimary")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorPrimaryContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSecondary")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSecondaryContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSuccess")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSuccessContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorWarning")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorWarningContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<float>("Depth")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("Noise")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("RadiusBox")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("RadiusField")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("RadiusSelector")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("SizeField")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("SizeSelector")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.HasKey("ThemeId");
|
||||
|
||||
b1.ToTable("Core_Themes");
|
||||
|
||||
b1.ToJson("Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ThemeId");
|
||||
});
|
||||
|
||||
b.Navigation("Content")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||
{
|
||||
b.Navigation("Parameters");
|
||||
|
||||
b.Navigation("QueuedJobs");
|
||||
|
||||
b.Navigation("States");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.ApiServer.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedThemes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Core_Themes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Author = table.Column<string>(type: "text", nullable: false),
|
||||
Version = table.Column<string>(type: "text", nullable: false),
|
||||
UpdateUrl = table.Column<string>(type: "text", nullable: true),
|
||||
DonateUrl = table.Column<string>(type: "text", nullable: true),
|
||||
Content = table.Column<string>(type: "jsonb", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Core_Themes", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Core_Themes");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,6 +302,40 @@ namespace Moonlight.ApiServer.Database.Migrations
|
||||
b.ToTable("Core_ApiKeys", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Core_Themes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -376,6 +410,143 @@ namespace Moonlight.ApiServer.Database.Migrations
|
||||
b.Navigation("Job");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||
{
|
||||
b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 =>
|
||||
{
|
||||
b1.Property<int>("ThemeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<float>("Border")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<string>("ColorAccent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorAccentContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBackground")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase100")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase150")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase200")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase250")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBase300")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorBaseContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorError")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorErrorContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorInfo")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorInfoContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorNeutral")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorNeutralContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorPrimary")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorPrimaryContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSecondary")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSecondaryContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSuccess")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorSuccessContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorWarning")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("ColorWarningContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<float>("Depth")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("Noise")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("RadiusBox")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("RadiusField")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("RadiusSelector")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("SizeField")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.Property<float>("SizeSelector")
|
||||
.HasColumnType("real");
|
||||
|
||||
b1.HasKey("ThemeId");
|
||||
|
||||
b1.ToTable("Core_Themes");
|
||||
|
||||
b1.ToJson("Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ThemeId");
|
||||
});
|
||||
|
||||
b.Navigation("Content")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||
{
|
||||
b.Navigation("Parameters");
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
using System.Text.Json;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.Shared.Http.Requests.Admin.Sys;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonCore.Models;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Mappers;
|
||||
using Moonlight.Shared.Http.Requests.Admin.Sys.Theme;
|
||||
using Moonlight.Shared.Http.Responses.Admin;
|
||||
|
||||
namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||
|
||||
@@ -9,15 +16,113 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||
[Route("api/admin/system/theme")]
|
||||
public class ThemeController : Controller
|
||||
{
|
||||
[HttpPatch]
|
||||
[Authorize(Policy = "permissions:admin.system.theme.update")]
|
||||
public async Task Patch([FromBody] UpdateThemeRequest request)
|
||||
{
|
||||
var themePath = Path.Combine("storage", "theme.json");
|
||||
private readonly DatabaseRepository<Theme> ThemeRepository;
|
||||
|
||||
await System.IO.File.WriteAllTextAsync(
|
||||
themePath,
|
||||
JsonSerializer.Serialize(request.Variables)
|
||||
);
|
||||
public ThemeController(DatabaseRepository<Theme> themeRepository)
|
||||
{
|
||||
ThemeRepository = themeRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.system.theme.read")]
|
||||
public async Task<PagedData<ThemeResponse>> Get(
|
||||
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||
[FromQuery] [Range(1, 100)] int pageSize
|
||||
)
|
||||
{
|
||||
var count = await ThemeRepository.Get().CountAsync();
|
||||
|
||||
var items = await ThemeRepository
|
||||
.Get()
|
||||
.Skip(page * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToArrayAsync();
|
||||
|
||||
var mappedItems = items
|
||||
.Select(ThemeMapper.ToResponse)
|
||||
.ToArray();
|
||||
|
||||
return new PagedData<ThemeResponse>()
|
||||
{
|
||||
CurrentPage = page,
|
||||
Items = mappedItems,
|
||||
PageSize = pageSize,
|
||||
TotalItems = count,
|
||||
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.system.theme.read")]
|
||||
public async Task<ThemeResponse> GetSingle([FromRoute] int id)
|
||||
{
|
||||
var theme = await ThemeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
if (theme == null)
|
||||
throw new HttpApiException("Theme with this id not found", 404);
|
||||
|
||||
return ThemeMapper.ToResponse(theme);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.system.theme.write")]
|
||||
public async Task<ThemeResponse> Create([FromBody] CreateThemeRequest request)
|
||||
{
|
||||
var theme = ThemeMapper.ToTheme(request);
|
||||
|
||||
var finalTheme = await ThemeRepository.Add(theme);
|
||||
|
||||
return ThemeMapper.ToResponse(finalTheme);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.system.theme.write")]
|
||||
public async Task<ThemeResponse> Update([FromRoute] int id, [FromBody] UpdateThemeRequest request)
|
||||
{
|
||||
var theme = await ThemeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
if (theme == null)
|
||||
throw new HttpApiException("Theme with this id not found", 404);
|
||||
|
||||
// Disable all other enabled themes if we are enabling the current theme.
|
||||
// This ensures only one theme is enabled at the time
|
||||
if (request.IsEnabled)
|
||||
{
|
||||
var otherThemes = await ThemeRepository
|
||||
.Get()
|
||||
.Where(x => x.IsEnabled && x.Id != id)
|
||||
.ToArrayAsync();
|
||||
|
||||
foreach (var otherTheme in otherThemes)
|
||||
otherTheme.IsEnabled = false;
|
||||
|
||||
await ThemeRepository.RunTransaction(set =>
|
||||
{
|
||||
set.UpdateRange(otherThemes);
|
||||
});
|
||||
}
|
||||
|
||||
ThemeMapper.Merge(theme, request);
|
||||
await ThemeRepository.Update(theme);
|
||||
|
||||
return ThemeMapper.ToResponse(theme);
|
||||
}
|
||||
|
||||
[HttpPost("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.system.theme.write")]
|
||||
public async Task Delete([FromRoute] int id)
|
||||
{
|
||||
var theme = await ThemeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (theme == null)
|
||||
throw new HttpApiException("Theme with this id not found", 404);
|
||||
|
||||
await ThemeRepository.Remove(theme);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class UsersController : Controller
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.users.get")]
|
||||
public async Task<IPagedData<UserResponse>> Get(
|
||||
[FromQuery] int page,
|
||||
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||
[FromQuery] [Range(1, 100)] int pageSize = 50
|
||||
)
|
||||
{
|
||||
|
||||
14
Moonlight.ApiServer/Mappers/ThemeMapper.cs
Normal file
14
Moonlight.ApiServer/Mappers/ThemeMapper.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.Shared.Http.Requests.Admin.Sys.Theme;
|
||||
using Moonlight.Shared.Http.Responses.Admin;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace Moonlight.ApiServer.Mappers;
|
||||
|
||||
[Mapper]
|
||||
public static partial class ThemeMapper
|
||||
{
|
||||
public static partial ThemeResponse ToResponse(Theme theme);
|
||||
public static partial Theme ToTheme(CreateThemeRequest request);
|
||||
public static partial void Merge([MappingTarget] Theme theme, UpdateThemeRequest request);
|
||||
}
|
||||
49
Moonlight.ApiServer/Models/ApplicationTheme.cs
Normal file
49
Moonlight.ApiServer/Models/ApplicationTheme.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Moonlight.ApiServer.Models;
|
||||
|
||||
public class ApplicationTheme
|
||||
{
|
||||
public string ColorBackground { get; set; }
|
||||
|
||||
public string ColorBase100 { get; set; }
|
||||
public string ColorBase150 { get; set; }
|
||||
public string ColorBase200 { get; set; }
|
||||
public string ColorBase250 { get; set; }
|
||||
public string ColorBase300 { get; set; }
|
||||
|
||||
public string ColorBaseContent { get; set; }
|
||||
|
||||
public string ColorPrimary { get; set; }
|
||||
public string ColorPrimaryContent { get; set; }
|
||||
|
||||
public string ColorSecondary { get; set; }
|
||||
public string ColorSecondaryContent { get; set; }
|
||||
|
||||
public string ColorAccent { get; set; }
|
||||
public string ColorAccentContent { get; set; }
|
||||
|
||||
public string ColorNeutral { get; set; }
|
||||
public string ColorNeutralContent { get; set; }
|
||||
|
||||
public string ColorInfo { get; set; }
|
||||
public string ColorInfoContent { get; set; }
|
||||
|
||||
public string ColorSuccess { get; set; }
|
||||
public string ColorSuccessContent { get; set; }
|
||||
|
||||
public string ColorWarning { get; set; }
|
||||
public string ColorWarningContent { get; set; }
|
||||
|
||||
public string ColorError { get; set; }
|
||||
public string ColorErrorContent { get; set; }
|
||||
|
||||
public float RadiusSelector { get; set; }
|
||||
public float RadiusField { get; set; }
|
||||
public float RadiusBox { get; set; }
|
||||
|
||||
public float SizeSelector { get; set; }
|
||||
public float SizeField { get; set; }
|
||||
|
||||
public float Border { get; set; }
|
||||
public float Depth { get; set; }
|
||||
public float Noise { get; set; }
|
||||
}
|
||||
@@ -33,6 +33,7 @@
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0-next.2" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
|
||||
Reference in New Issue
Block a user