diff --git a/Moonlight/Core/Database/Migrations/20240127164420_FixedMissingPropertyInServerImage.Designer.cs b/Moonlight/Core/Database/Migrations/20240127164420_FixedMissingPropertyInServerImage.Designer.cs new file mode 100644 index 00000000..5be6d5eb --- /dev/null +++ b/Moonlight/Core/Database/Migrations/20240127164420_FixedMissingPropertyInServerImage.Designer.cs @@ -0,0 +1,1040 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.Core.Database; + +#nullable disable + +namespace Moonlight.Core.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20240127164420_FixedMissingPropertyInServerImage")] + partial class FixedMissingPropertyInServerImage + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.2"); + + modelBuilder.Entity("Moonlight.Core.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Avatar") + .HasColumnType("TEXT"); + + b.Property("Balance") + .HasColumnType("REAL"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Flags") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("INTEGER"); + + b.Property("TokenValidTimestamp") + .HasColumnType("TEXT"); + + b.Property("TotpKey") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.PostComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PostId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("PostId"); + + b.ToTable("PostComments"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.PostLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PostId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("PostLikes"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.WordFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Filter") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WordFilters"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Cpu") + .HasColumnType("INTEGER"); + + b.Property("Disk") + .HasColumnType("INTEGER"); + + b.Property("DockerImageIndex") + .HasColumnType("INTEGER"); + + b.Property("ImageId") + .HasColumnType("INTEGER"); + + b.Property("MainAllocationId") + .HasColumnType("INTEGER"); + + b.Property("Memory") + .HasColumnType("INTEGER"); + + b.Property("NodeId") + .HasColumnType("INTEGER"); + + b.Property("OverrideStartupCommand") + .HasColumnType("TEXT"); + + b.Property("ServiceId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServiceId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("ServerNodeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("ServerNodeId"); + + b.ToTable("ServerAllocations"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerDockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoPull") + .HasColumnType("INTEGER"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ServerImageId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ServerImageId"); + + b.ToTable("ServerDockerImages"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllocationsNeeded") + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DefaultDockerImageIndex") + .HasColumnType("INTEGER"); + + b.Property("DonateUrl") + .HasColumnType("TEXT"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("InstallShell") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OnlineDetection") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ParseConfigurations") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartupCommand") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdateUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ServerImages"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowUserToEdit") + .HasColumnType("INTEGER"); + + b.Property("AllowUserToView") + .HasColumnType("INTEGER"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ServerImageId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ServerImageId"); + + b.ToTable("ServerImageVariables"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FtpPort") + .HasColumnType("INTEGER"); + + b.Property("HttpPort") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Token") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UseSsl") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ServerNodes"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.Features.ServiceManagement.Entities.Service", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConfigJsonOverride") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Nickname") + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .HasColumnType("INTEGER"); + + b.Property("ProductId") + .HasColumnType("INTEGER"); + + b.Property("RenewAt") + .HasColumnType("TEXT"); + + b.Property("Suspended") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ProductId"); + + b.ToTable("Services"); + }); + + modelBuilder.Entity("Moonlight.Features.ServiceManagement.Entities.ServiceShare", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ServiceId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId"); + + b.HasIndex("UserId"); + + b.ToTable("ServiceShares"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.Coupon", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Percent") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Coupons"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.CouponUse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CouponId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CouponId"); + + b.HasIndex("UserId"); + + b.ToTable("CouponUses"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.GiftCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("GiftCodes"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.GiftCodeUse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GiftCodeId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GiftCodeId"); + + b.HasIndex("UserId"); + + b.ToTable("GiftCodeUses"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("MaxPerUser") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("REAL"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Stock") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("REAL"); + + b.Property("Text") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Moonlight.Features.Theming.Entities.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CssUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DonateUrl") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("JsUrl") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Themes"); + }); + + modelBuilder.Entity("Moonlight.Features.Ticketing.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Open") + .HasColumnType("INTEGER"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("ServiceId") + .HasColumnType("INTEGER"); + + b.Property("Tries") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("ServiceId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Moonlight.Features.Ticketing.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attachment") + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsSupport") + .HasColumnType("INTEGER"); + + b.Property("SenderId") + .HasColumnType("INTEGER"); + + b.Property("TicketId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SenderId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.Post", b => + { + b.HasOne("Moonlight.Core.Database.Entities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.PostComment", b => + { + b.HasOne("Moonlight.Core.Database.Entities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Features.Community.Entities.Post", null) + .WithMany("Comments") + .HasForeignKey("PostId"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.PostLike", b => + { + b.HasOne("Moonlight.Features.Community.Entities.Post", null) + .WithMany("Likes") + .HasForeignKey("PostId"); + + b.HasOne("Moonlight.Core.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.Server", b => + { + b.HasOne("Moonlight.Features.Servers.Entities.ServerImage", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Features.Servers.Entities.ServerAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Features.Servers.Entities.ServerNode", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Features.ServiceManagement.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerAllocation", b => + { + b.HasOne("Moonlight.Features.Servers.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + + b.HasOne("Moonlight.Features.Servers.Entities.ServerNode", null) + .WithMany("Allocations") + .HasForeignKey("ServerNodeId"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerDockerImage", b => + { + b.HasOne("Moonlight.Features.Servers.Entities.ServerImage", null) + .WithMany("DockerImages") + .HasForeignKey("ServerImageId"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerImageVariable", b => + { + b.HasOne("Moonlight.Features.Servers.Entities.ServerImage", null) + .WithMany("Variables") + .HasForeignKey("ServerImageId"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.Features.Servers.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.Features.ServiceManagement.Entities.Service", b => + { + b.HasOne("Moonlight.Core.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Features.StoreSystem.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Moonlight.Features.ServiceManagement.Entities.ServiceShare", b => + { + b.HasOne("Moonlight.Features.ServiceManagement.Entities.Service", null) + .WithMany("Shares") + .HasForeignKey("ServiceId"); + + b.HasOne("Moonlight.Core.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.CouponUse", b => + { + b.HasOne("Moonlight.Features.StoreSystem.Entities.Coupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Core.Database.Entities.User", null) + .WithMany("CouponUses") + .HasForeignKey("UserId"); + + b.Navigation("Coupon"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.GiftCodeUse", b => + { + b.HasOne("Moonlight.Features.StoreSystem.Entities.GiftCode", "GiftCode") + .WithMany() + .HasForeignKey("GiftCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Core.Database.Entities.User", null) + .WithMany("GiftCodeUses") + .HasForeignKey("UserId"); + + b.Navigation("GiftCode"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.Product", b => + { + b.HasOne("Moonlight.Features.StoreSystem.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Moonlight.Features.StoreSystem.Entities.Transaction", b => + { + b.HasOne("Moonlight.Core.Database.Entities.User", null) + .WithMany("Transactions") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Moonlight.Features.Ticketing.Entities.Ticket", b => + { + b.HasOne("Moonlight.Core.Database.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.Features.ServiceManagement.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId"); + + b.Navigation("Creator"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("Moonlight.Features.Ticketing.Entities.TicketMessage", b => + { + b.HasOne("Moonlight.Core.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.HasOne("Moonlight.Features.Ticketing.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.Core.Database.Entities.User", b => + { + b.Navigation("CouponUses"); + + b.Navigation("GiftCodeUses"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Moonlight.Features.Community.Entities.Post", b => + { + b.Navigation("Comments"); + + b.Navigation("Likes"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerImage", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerNode", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.Features.ServiceManagement.Entities.Service", b => + { + b.Navigation("Shares"); + }); + + modelBuilder.Entity("Moonlight.Features.Ticketing.Entities.Ticket", b => + { + b.Navigation("Messages"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/Core/Database/Migrations/20240127164420_FixedMissingPropertyInServerImage.cs b/Moonlight/Core/Database/Migrations/20240127164420_FixedMissingPropertyInServerImage.cs new file mode 100644 index 00000000..f656bc99 --- /dev/null +++ b/Moonlight/Core/Database/Migrations/20240127164420_FixedMissingPropertyInServerImage.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.Core.Database.Migrations +{ + /// + public partial class FixedMissingPropertyInServerImage : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ServerImageId", + table: "ServerImageVariables", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_ServerImageVariables_ServerImageId", + table: "ServerImageVariables", + column: "ServerImageId"); + + migrationBuilder.AddForeignKey( + name: "FK_ServerImageVariables_ServerImages_ServerImageId", + table: "ServerImageVariables", + column: "ServerImageId", + principalTable: "ServerImages", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ServerImageVariables_ServerImages_ServerImageId", + table: "ServerImageVariables"); + + migrationBuilder.DropIndex( + name: "IX_ServerImageVariables_ServerImageId", + table: "ServerImageVariables"); + + migrationBuilder.DropColumn( + name: "ServerImageId", + table: "ServerImageVariables"); + } + } +} diff --git a/Moonlight/Core/Database/Migrations/DataContextModelSnapshot.cs b/Moonlight/Core/Database/Migrations/DataContextModelSnapshot.cs index fbc8ffb5..cd9d4e14 100644 --- a/Moonlight/Core/Database/Migrations/DataContextModelSnapshot.cs +++ b/Moonlight/Core/Database/Migrations/DataContextModelSnapshot.cs @@ -353,8 +353,13 @@ namespace Moonlight.Core.Database.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("ServerImageId") + .HasColumnType("INTEGER"); + b.HasKey("Id"); + b.HasIndex("ServerImageId"); + b.ToTable("ServerImageVariables"); }); @@ -856,6 +861,13 @@ namespace Moonlight.Core.Database.Migrations .HasForeignKey("ServerImageId"); }); + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerImageVariable", b => + { + b.HasOne("Moonlight.Features.Servers.Entities.ServerImage", null) + .WithMany("Variables") + .HasForeignKey("ServerImageId"); + }); + modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerVariable", b => { b.HasOne("Moonlight.Features.Servers.Entities.Server", null) @@ -1001,6 +1013,8 @@ namespace Moonlight.Core.Database.Migrations modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerImage", b => { b.Navigation("DockerImages"); + + b.Navigation("Variables"); }); modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerNode", b => diff --git a/Moonlight/Core/Helpers/ValidatorHelper.cs b/Moonlight/Core/Helpers/ValidatorHelper.cs new file mode 100644 index 00000000..35614cf4 --- /dev/null +++ b/Moonlight/Core/Helpers/ValidatorHelper.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using Moonlight.Core.Exceptions; + +namespace Moonlight.Core.Helpers; + +public class ValidatorHelper +{ + public static Task Validate(object objectToValidate) + { + var context = new ValidationContext(objectToValidate, null, null); + var results = new List(); + + var isValid = Validator.TryValidateObject(objectToValidate, context, results, true); + + if (!isValid) + { + var errorMsg = "Unknown form error"; + + if (results.Any()) + errorMsg = results.First().ErrorMessage ?? errorMsg; + + throw new DisplayException(errorMsg); + } + + return Task.CompletedTask; + } + + public static async Task ValidateRange(IEnumerable objectToValidate) + { + foreach (var o in objectToValidate) + await Validate(o); + } +} \ No newline at end of file diff --git a/Moonlight/Core/UI/Components/Forms/AutoListCrud.razor b/Moonlight/Core/UI/Components/Forms/AutoListCrud.razor new file mode 100644 index 00000000..3945be9d --- /dev/null +++ b/Moonlight/Core/UI/Components/Forms/AutoListCrud.razor @@ -0,0 +1,244 @@ +@using BlazorTable +@using System.Linq.Expressions +@using Mappy.Net +@using Moonlight.Core.Services.Interop + +@typeparam TItem where TItem : class +@typeparam TRootItem where TRootItem : class +@typeparam TCreateForm +@typeparam TUpdateForm + +@inject ToastService ToastService + + + + @(Title) + + @Toolbar + + + + + + + + + @View + + + + + StartUpdate(context)" class="btn btn-icon btn-warning"> + + + StartDelete(context)" class="btn btn-icon btn-danger"> + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + Update + + + + + + + + + + + + + + + Do you want to delete this item? + + + + + This action cannot be undone. The data will be deleted and cannot be restored + + + + + +@code +{ + [Parameter] + public string Title { get; set; } = ""; + + [Parameter] + public TRootItem RootItem { get; set; } + + [Parameter] + public Func> Field { get; set; } + + [Parameter] + public RenderFragment View { get; set; } + + [Parameter] + public RenderFragment Toolbar { get; set; } + + [Parameter] + public Func? ValidateAdd { get; set; } + + [Parameter] + public Func? ValidateUpdate { get; set; } + + [Parameter] + public Func? ValidateDelete { get; set; } + + private TItem[] Items; + private TCreateForm CreateForm; + private TUpdateForm UpdateForm; + private TItem ItemToUpdate; + private TItem ItemToDelete; + + private SmartModal CreateModal; + private SmartModal UpdateModal; + private SmartModal DeleteModal; + + private Expression> IdExpression; + private LazyLoader LazyLoader; + + protected override void OnInitialized() + { + if (Field == null) + throw new ArgumentNullException(nameof(Field)); + + CreateForm = Activator.CreateInstance()!; + UpdateForm = Activator.CreateInstance()!; + + IdExpression = CreateExpression(); + } + + public async Task Reload() => await LazyLoader.Reload(); + + private Task LoadItems(LazyLoader _) + { + Items = Field.Invoke(RootItem).ToArray(); + + return Task.CompletedTask; + } + + private async Task StartUpdate(TItem item) + { + UpdateForm = Mapper.Map(item); + ItemToUpdate = item; + await UpdateModal.Show(); + } + + private async Task FinishUpdate() + { + var item = Mapper.Map(ItemToUpdate, UpdateForm!); + + if (ValidateUpdate != null) // Optional additional validation + await ValidateUpdate.Invoke(item); + + //ItemRepository.Update(item); + + // Reset + await UpdateModal.Hide(); + await LazyLoader.Reload(); + await ToastService.Success("Successfully updated item"); + } + + private async Task StartCreate() + { + CreateForm = Activator.CreateInstance()!; + await CreateModal.Show(); + } + + private async Task FinishCreate() + { + var item = Mapper.Map(CreateForm!); + + if (ValidateAdd != null) // Optional additional validation + await ValidateAdd.Invoke(item); + + Field.Invoke(RootItem).Add(item); + + // Reset + await CreateModal.Hide(); + await LazyLoader.Reload(); + await ToastService.Success("Successfully added item"); + } + + private async Task StartDelete(TItem item) + { + ItemToDelete = item; + await DeleteModal.Show(); + } + + private async Task FinishDelete() + { + if (ValidateDelete != null) // Optional additional validation + await ValidateDelete.Invoke(ItemToDelete); + + Field.Invoke(RootItem).Remove(ItemToDelete); + + // Reset + await DeleteModal.Hide(); + await LazyLoader.Reload(); + await ToastService.Success("Successfully deleted item"); + } + + private Expression> CreateExpression() + { + // Parameter expression for the input object + var inputParam = Expression.Parameter(typeof(TItem), "input"); + + // Convert the input object to the actual model type (MyModel in this example) + var castedInput = Expression.Convert(inputParam, typeof(TItem)); + + // Create a property access expression using the property name + var propertyAccess = Expression.Property(castedInput, "Id"); + + // Convert the property value to an object + var castedResult = Expression.Convert(propertyAccess, typeof(object)); + + // Create a lambda expression + var lambda = Expression.Lambda>(castedResult, inputParam); + + return lambda; + } +} \ No newline at end of file diff --git a/Moonlight/Features/Servers/Entities/ServerImage.cs b/Moonlight/Features/Servers/Entities/ServerImage.cs index 464df691..485df193 100644 --- a/Moonlight/Features/Servers/Entities/ServerImage.cs +++ b/Moonlight/Features/Servers/Entities/ServerImage.cs @@ -19,7 +19,7 @@ public class ServerImage public string? DonateUrl { get; set; } public string? UpdateUrl { get; set; } - public List Variables = new(); + public List Variables { get; set; } = new(); public int DefaultDockerImageIndex { get; set; } = 0; public List DockerImages { get; set; } diff --git a/Moonlight/Features/Servers/Models/Forms/Admin/Images/ParseConfigForm.cs b/Moonlight/Features/Servers/Models/Forms/Admin/Images/ParseConfigForm.cs new file mode 100644 index 00000000..ecff53e9 --- /dev/null +++ b/Moonlight/Features/Servers/Models/Forms/Admin/Images/ParseConfigForm.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Features.Servers.Models.Forms.Admin.Images; + +public class ParseConfigForm +{ + [Required(ErrorMessage = "You need to specify a type in a parse configuration")] + public string Type { get; set; } = ""; + + [Required(ErrorMessage = "You need to specify a file in a parse configuration")] + public string File { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/Features/Servers/Models/Forms/Admin/Images/ParseConfigOptionForm.cs b/Moonlight/Features/Servers/Models/Forms/Admin/Images/ParseConfigOptionForm.cs new file mode 100644 index 00000000..0fdc0b4f --- /dev/null +++ b/Moonlight/Features/Servers/Models/Forms/Admin/Images/ParseConfigOptionForm.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Features.Servers.Models.Forms.Admin.Images; + +public class ParseConfigOptionForm +{ + [Required(ErrorMessage = "You need to specify the key of an option")] + public string Key { get; set; } = ""; + public string Value { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/Features/Servers/Models/ServerParseConfig.cs b/Moonlight/Features/Servers/Models/ServerParseConfig.cs new file mode 100644 index 00000000..417c8093 --- /dev/null +++ b/Moonlight/Features/Servers/Models/ServerParseConfig.cs @@ -0,0 +1,8 @@ +namespace Moonlight.Features.Servers.Models; + +public class ServerParseConfig +{ + public string Type { get; set; } = ""; + public string File { get; set; } = ""; + public Dictionary Configuration { get; set; } = new(); +} \ No newline at end of file diff --git a/Moonlight/Features/Servers/UI/Components/ImageEditor.razor b/Moonlight/Features/Servers/UI/Components/ImageEditor.razor new file mode 100644 index 00000000..5bddd999 --- /dev/null +++ b/Moonlight/Features/Servers/UI/Components/ImageEditor.razor @@ -0,0 +1,154 @@ + + + + General + + + + + Name + + + + Author + + + + Donate URL + + (Optional) URL to link donation pages for the author + + + + + Update URL + + (Optional) URL to enable auto updates on images. This link needs to be a direct download link to a json file + + + + + + + + + Installation + + + + + Install docker image + + This specifies the docker image to use for the script execution + + + + + Install shell + + This is the shell to pass the install script to + + + + + @* TODO: Add vscode editor or similar *@ + Install script + + + + + + Startup, Control & Allocations + + + Startup command + + This command gets passed to the container of the image to execute. Server variables can be used here + + + + + Stop command + + This command will get written into the input stream of the server process when the server should get stopped + + + + + Online detection + + The regex string you specify here will be used in order to detect if a server is up and running + + + + + Allocations Amount + + The allocations (aka. ports) a image needs in order to be created + + + + + + + + + Docker images + + + + + + + + + @foreach (var dockerImage in DockerImages) + { + + + + Remove + + + } + + + + + + Variables + + + + + + + + + @foreach (var variable in Variables) + { + + + + + Remove + + + } + + + + + + + + + Save changes + + + + +@code +{ + +} diff --git a/Moonlight/Features/Servers/UI/Components/ParseConfigEditor.razor b/Moonlight/Features/Servers/UI/Components/ParseConfigEditor.razor new file mode 100644 index 00000000..2f5e40b5 --- /dev/null +++ b/Moonlight/Features/Servers/UI/Components/ParseConfigEditor.razor @@ -0,0 +1,156 @@ +@using Newtonsoft.Json +@using Mappy.Net +@using Moonlight.Core.Helpers +@using Moonlight.Features.Servers.Models +@using Moonlight.Features.Servers.Models.Forms.Admin.Images + + + + Parse configurations + + + + + + + + @foreach (var config in Configs) + { + + + + + @(string.IsNullOrEmpty(config.Key.File) ? "File path is missing" : config.Key.File) + + + + + + + File path + + A relative path from the servers main directory to the file you want to modify + + + + + Type + + This specifies the type of parser to use. e.g. "properties" or "file" + + + + + + Remove + + + + + + + + AddOption(config.Key)" type="button" class="btn btn-icon btn-success"> + + + + + + + @foreach (var option in config.Value) + { + + + + + Remove + + + } + + + + + + + + } + + + +@code +{ + [Parameter] + public string InitialContent { get; set; } = ""; + + private Dictionary> Configs = new(); + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && !string.IsNullOrEmpty(InitialContent)) + await Set(InitialContent); + } + + public async Task Set(string content) + { + Configs.Clear(); + + var configs = JsonConvert.DeserializeObject(content) + ?? Array.Empty(); + + foreach (var config in configs) + { + var options = config.Configuration.Select(x => new ParseConfigOptionForm() + { + Key = x.Key, + Value = x.Value + }).ToList(); + + + Configs.Add(Mapper.Map(config), options); + } + + await InvokeAsync(StateHasChanged); + } + + public async Task ValidateAndGet() + { + await ValidatorHelper.ValidateRange(Configs.Keys); + + foreach (var options in Configs.Values) + await ValidatorHelper.ValidateRange(options); + + var finalConfigs = Configs.Select(x => new ServerParseConfig() + { + File = x.Key.File, + Type = x.Key.Type, + Configuration = x.Value.ToDictionary(y => y.Key, y => y.Value) + }).ToList(); + + return JsonConvert.SerializeObject(finalConfigs); + } + + private async Task AddConfig() + { + Configs.Add(new(), new()); + await InvokeAsync(StateHasChanged); + } + + private async Task RemoveConfig(ParseConfigForm config) + { + Configs.Remove(config); + await InvokeAsync(StateHasChanged); + } + + private async Task AddOption(ParseConfigForm config) + { + Configs[config].Add(new()); + await InvokeAsync(StateHasChanged); + } + + private async Task RemoveOption(ParseConfigForm config, ParseConfigOptionForm option) + { + Configs[config].Remove(option); + await InvokeAsync(StateHasChanged); + } +} \ No newline at end of file diff --git a/Moonlight/Features/Servers/UI/Views/Admin/Images/Index.razor b/Moonlight/Features/Servers/UI/Views/Admin/Images/Index.razor new file mode 100644 index 00000000..eba6f842 --- /dev/null +++ b/Moonlight/Features/Servers/UI/Views/Admin/Images/Index.razor @@ -0,0 +1,129 @@ +@page "/admin/servers/images" + +@using BlazorTable +@using Microsoft.EntityFrameworkCore +@using Moonlight.Core.Exceptions +@using Moonlight.Core.Repositories +@using Moonlight.Core.Services.Interop +@using Moonlight.Features.Servers.Entities + +@inject Repository ImageRepository +@inject Repository ImageVariableRepository +@inject Repository DockerImageRepository +@inject Repository ServerRepository +@inject ToastService ToastService + + + + Manage server images + + + + + + + + + + + + + + + @if (!string.IsNullOrEmpty(context.UpdateUrl)) + { + + Update + + + } + @if (!string.IsNullOrEmpty(context.DonateUrl)) + { + + Donate + + + } + + + + + + + + + + + + + + + + + + + + + +@code +{ + private LazyLoader LazyLoader; + private ServerImage[] Images; + + private Task Load(LazyLoader arg) + { + Images = ImageRepository + .Get() + .ToArray(); + + return Task.CompletedTask; + } + + private async Task Update(ServerImage image) + { + } + + private async Task Delete(ServerImage image) + { + var anyServerWithThisImage = ServerRepository + .Get() + .Any(x => x.Image.Id == image.Id); + + if (anyServerWithThisImage) + throw new DisplayException("This image cannot be deleted, because one or more server depend on it. Delete the server(s) first and then delete the image"); + + var imageWithData = ImageRepository + .Get() + .Include(x => x.Variables) + .Include(x => x.DockerImages) + .First(x => x.Id == image.Id); + + // Cache and clear relational data + + var variables = imageWithData.Variables.ToArray(); + var dockerImages = imageWithData.DockerImages.ToArray(); + + imageWithData.DockerImages.Clear(); + imageWithData.Variables.Clear(); + + ImageRepository.Update(imageWithData); + + foreach (var variable in variables) + ImageVariableRepository.Delete(variable); + + foreach (var dockerImage in dockerImages) + DockerImageRepository.Delete(dockerImage); + + // And now we can clear the image + + ImageRepository.Delete(imageWithData); + + // and notify the user + await ToastService.Success("Successfully deleted image"); + await LazyLoader.Reload(); + } +} \ No newline at end of file diff --git a/Moonlight/Features/Servers/UI/Views/Admin/Images/New.razor b/Moonlight/Features/Servers/UI/Views/Admin/Images/New.razor new file mode 100644 index 00000000..f170d79d --- /dev/null +++ b/Moonlight/Features/Servers/UI/Views/Admin/Images/New.razor @@ -0,0 +1,10 @@ +@page "/admin/servers/images/new" + +@using Moonlight.Features.Servers.Entities + + + +@code +{ + private ServerImage Image; +}
+ This action cannot be undone. The data will be deleted and cannot be restored +